Mastering Go Project Structure: An Industry-Standard Guide
golang
Structuring a Go application effectively is key to its long-term success. While Go offers flexibility, the community has adopted powerful patterns for building scalable and maintainable services. This guide dives into the industry-standard layout, focusing on how to organize your code from the entrypoint down to the business logic.
The Standard Layout: A High-Level View
A widely recognized template for Go applications is the golang-standards/project-layout. You should treat this as a comprehensive guide, not a rigid mandate. Adopt only the directories you need.
Let’s break down the most critical directories.
/cmd
This is where the entry points to your application live. Each subdirectory is a main
package for a specific executable.
/cmd
├── /my-api
│ └── main.go
└── /my-worker
└── main.go
The main
function’s job is to be minimal. It should only handle initialization and act as the glue for your application, wiring together dependencies like the database, logger, and your business logic layers.
/internal
This is the heart of your application. The Go compiler enforces a special rule for the internal
directory: its code can only be imported by other code within the same parent project. This makes it the perfect place for all your private, application-specific business logic.
Within /internal
, it’s common to adopt a layered or domain-driven architecture. This is where patterns like service
and repository
come into play.
/internal
├── /api # Your HTTP/gRPC handlers and routers
├── /service # Business logic layer
├── /repository # Data access layer
└── /domain # Core data structures and domain logic
Let’s explore these layers:
-
/domain
(or/model
): This package contains the core data structures of your application. These are the plain Go structs that represent your business entities (e.g.,User
,Product
). They should have no dependencies on other layers. -
/repository
(or/store
): This layer is responsible for all data persistence. It abstracts the database (PostgreSQL, MongoDB, etc.) from the rest of your application. It typically defines interfaces for database operations (e.g.,UserRepository
) and provides concrete implementations. Your business logic should only ever depend on the interfaces, not the concrete database code. -
/service
: This is the core business logic layer. It orchestrates operations, calling methods on the repository layer to fetch and store data, and executing the business rules specific to your domain. For example, aUserService
might have aCreate
method that validates a new user, calls therepository.CreateUser
method, and then sends a welcome email. -
/api
(or/handler
): This layer is responsible for handling incoming requests (e.g., HTTP or gRPC). It decodes requests, calls the appropriate methods on theservice
layer, and then encodes the response. It should not contain any business logic.
/pkg
This directory is for code that is safe to be imported and used by external applications. Before you place a package here, ask yourself: “Will another project ever need to import this?” If the answer is no, it belongs in /internal
.
This is a critical point. Your application’s specific database client or Cloudinary service is not something another project will import. Therefore, they do not belong in /pkg
. They are internal details of your service.
Other Useful Directories
- /api: For API contract files, like OpenAPI specs or Protobuf definitions. Note the distinction from
/internal/api
, which contains the handler implementation. - /configs: Configuration files (e.g.,
config.yaml
). - /scripts: Helper scripts for building, testing, or deploying.
Handling Configuration and Secrets
For configuration, use a combination of files for default values and environment variables for secrets.
Your /configs/config.yaml
file should only contain non-sensitive, default information.
# /configs/config.yaml
server:
port: "8080"
database:
host: "localhost"
port: "5432"
user: "myuser"
dbname: "mydatabase"
Secrets, such as database passwords or JWT keys, must be loaded from the environment and should not be stored in files. For local development, these can be managed in a .env
file that is excluded from source control.
A Practical Example: Layered REST API
Let’s refine our User service API with this layered structure. The logic inside main
should follow these steps:
// in /cmd/user-api/main.go
// 1. Load configuration
// Read from /configs/config.yaml and environment variables.
// If secrets are missing, exit the application immediately.
// 2. Initialize dependencies
// - Create the database connection string from the loaded config.
// - Initialize the database connection pool.
// - Initialize clients for any external services (e.g., Cloudinary).
// 3. Inject dependencies into layers
// - Create an instance of the repository, passing it the database connection.
// - Create an instance of the service, passing it the repository and any secrets.
// - Create an instance of the API handler, passing it the service.
// 4. Setup router and start the server
// - Pass the handler instance to the router.
// - Start the HTTP server on the port specified in the config.
Our final structure provides a clean separation of concerns and makes the flow of configuration explicit.
/user-service
├── go.mod
├── .env <-- For local secrets (add to .gitignore)
├── /cmd/user-api/
│ └── main.go
├── /internal/
│ ├── /api/
│ │ ├── handlers.go
│ │ └── router.go
│ ├── /config/
│ │ └── config.go
│ ├── /cloudinary/
│ │ └── client.go
│ ├── /service/
│ │ └── user_service.go
│ ├── /repository/
│ │ └── user_repository.go
│ └── /domain/
│ └── user.go
├── /api/
│ └── openapi.yaml
└── /configs/
└── config.yaml
Key Principles
- Dependency Injection: Wire components together from
main
(as seen in the example). This makes your code testable and decoupled. - Depend on Interfaces: Your services should depend on repository interfaces, not concrete types. This allows you to easily mock the database during testing.
- Clear Boundaries: Each layer has a distinct responsibility. Don’t let logic from one layer leak into another.
By adopting this layered approach within the standard Go project layout, you create applications that are robust, highly testable, and easy to maintain as they grow in complexity.
Latest Posts
Mastering Python Context Managers: A Guide to the `with` Statement
Go beyond `with open()` and learn how to build your own context managers in Python. This guide covers both class-based and decorator patterns for robust resource management.
Mastering Python Generators for Memory-Efficient Iteration
Learn the power of Python generators to handle large datasets with ease. Discover what they are, why, when, and when not to use them for cleaner, more efficient code.
Mastering SQLModel: A Guide to Python Database Best Practices
A comprehensive guide to using SQLModel effectively in your Python applications, covering core concepts and essential best practices for robust database management.
Enjoyed this article? Follow me on X for more content and updates!
Follow @Ctrixdev