Introduction
The golang-standards/project-layout repository is one of the most influential resources in the Go ecosystem, boasting 55,271 stars on GitHub (as of this writing). While not an official standard from the Go team, this community-maintained project documents widely adopted patterns for organizing Go projects at scale. It serves as a battle-tested template for structuring production-grade applications, libraries, and services.
For Go developers, project organization becomes critical when:
- Building applications that outgrow a single
main.gofile - Collaborating with teams that need clear boundaries between components
- Creating reusable libraries consumed by other projects
- Implementing CI/CD pipelines that require standardized test/build locations
The layout solves common problems like:
- Codebase discoverability: Where to find tests, configuration, or business logic
- Dependency management: Separating internal and external packages
- Build optimization: Organizing binaries, assets, and deployment artifacts
- Testing consistency: Locating unit, integration, and end-to-end tests
Real-world adopters include infrastructure tools (Terraform providers), web services (REST APIs), and enterprise applications requiring clear separation between domains.
Key Features
-
/cmdDirectory- Contains main applications for your project
- Each subdirectory represents a standalone binary (e.g.,
cmd/server,cmd/cli) - Prevents dependency collisions between multiple executables
-
/internalDirectory- Stores private application code not meant for external consumption
- Enforced by Go’s compiler to prevent imports from outside your project
- Ideal for business logic and domain-specific implementations
-
/pkgDirectory- Houses public code that others can import
- Used when developing reusable libraries/components
- Clearly distinguishes exported vs internal functionality
-
Standardized Testing Locations
TestMainsetup in/pkgor/internalpackages/testdirectory for integration/end-to-end tests/apifor Protobuf/OpenAPI specs and contract tests
-
Build/Deployment Support
/buildfor CI/CD scripts and Dockerfiles/configsfor deployment configurations/deploymentsfor orchestration templates (Kubernetes, Terraform)
Compared to flat structures, this layout explicitly defines:
1project-root/
2├── cmd/
3├── internal/
4├── pkg/
5├── test/
6└── ...other standard dirs
Installation and Setup
This isn’t a traditional library—it’s a project template. To use it:
- Clone the repository as a reference:
1git clone https://github.com/golang-standards/project-layout.git
- Create a new project with the same structure:
1mkdir -p my-project/{cmd,internal,pkg,test}
Requirements:
- Go 1.20+ (for modern module support)
- No external dependencies required
Verify your structure matches core directories:
1tree -d -L 1 my-project
2# Should show: cmd, internal, pkg, test
Basic Usage
Minimal Web Server Example
Directory structure:
1my-app/
2├── cmd/
3│ └── server/
4│ └── main.go
5├── internal/
6│ └── handler/
7│ └── handler.go
8└── go.mod
internal/handler/handler.go:
1package handler
2
3import (
4 "fmt"
5 "net/http"
6)
7
8func Hello(w http.ResponseWriter, r *http.Request) {
9 fmt.Fprint(w, "Hello from structured project!")
10}
cmd/server/main.go:
1package main
2
3import (
4 "log"
5 "net/http"
6 "my-app/internal/handler"
7)
8
9func main() {
10 mux := http.NewServeMux()
11 mux.HandleFunc("/", handler.Hello)
12
13 log.Println("Server running on :8080")
14 log.Fatal(http.ListenAndServe(":8080", mux))
15}
Run and test:
1go run cmd/server/main.go
2# curl localhost:8080 → "Hello from structured project!"
Real-World Examples
Example 1: Production REST API
Structure:
1api/
2├── cmd/
3│ ├── api-server/
4│ │ └── main.go
5│ └── migrate/
6│ └── main.go
7├── internal/
8│ ├── user/
9│ │ ├── service.go
10│ │ └── handler.go
11│ └── middleware/
12│ └── auth.go
13├── pkg/
14│ └── database/
15│ └── postgres.go
16└── test/
17 └── integration/
18 └── user_test.go
pkg/database/postgres.go (simplified):
1package database
2
3import (
4 "context"
5 "database/sql"
6 _ "github.com/lib/pq"
7)
8
9type DB struct {
10 *sql.DB
11}
12
13func New(ctx context.Context, connStr string) (*DB, error) {
14 db, err := sql.Open("postgres", connStr)
15 if err != nil {
16 return nil, err
17 }
18
19 if err := db.PingContext(ctx); err != nil {
20 return nil, err
21 }
22
23 return &DB{db}, nil
24}
internal/user/handler.go:
1package user
2
3import (
4 "encoding/json"
5 "net/http"
6)
7
8type Service interface {
9 GetUser(id string) (*User, error)
10}
11
12type Handler struct {
13 svc Service
14}
15
16func NewHandler(svc Service) *Handler {
17 return &Handler{svc: svc}
18}
19
20func (h *Handler) GetUser(w http.ResponseWriter, r *http.Request) {
21 user, err := h.svc.GetUser(r.PathValue("id"))
22 if err != nil {
23 http.Error(w, err.Error(), http.StatusNotFound)
24 return
25 }
26
27 json.NewEncoder(w).Encode(user)
28}
cmd/api-server/main.go:
1package main
2
3import (
4 "context"
5 "log"
6 "net/http"
7
8 "my-api/internal/user"
9 "my-api/internal/middleware"
10 "my-api/pkg/database"
11)
12
13func main() {
14 ctx := context.Background()
15
16 // Initialize dependencies
17 db, err := database.New(ctx, "postgres://...")
18 if err != nil {
19 log.Fatal(err)
20 }
21 defer db.Close()
22
23 userSvc := user.NewService(db)
24 userHandler := user.NewHandler(userSvc)
25
26 // Setup routes
27 mux := http.NewServeMux()
28 mux.HandleFunc("GET /users/{id}", userHandler.GetUser)
29
30 // Add middleware
31 wrappedMux := middleware.Auth(mux)
32
33 log.Println("API server starting on :8080")
34 log.Fatal(http.ListenAndServe(":8080", wrappedMux))
35}
Example 2: CLI Tool with Subcommands
Structure:
1cli-tool/
2├── cmd/
3│ ├── cli/
4│ │ └── main.go
5├── internal/
6│ └── command/
7│ ├── root.go
8│ ├── generate.go
9│ └── validate.go
10└── pkg/
11 └── utils/
12 └── file.go
cmd/cli/main.go:
1package main
2
3import (
4 "os"
5
6 "cli-tool/internal/command"
7 "github.com/spf13/cobra"
8)
9
10func main() {
11 rootCmd := command.NewRootCmd()
12 rootCmd.AddCommand(
13 command.NewGenerateCmd(),
14 command.NewValidateCmd(),
15 )
16
17 if err := rootCmd.Execute(); err != nil {
18 os.Exit(1)
19 }
20}
internal/command/root.go:
1package command
2
3import (
4 "github.com/spf13/cobra"
5)
6
7func NewRootCmd() *cobra.Command {
8 return &cobra.Command{
9 Use: "mytool",
10 Short: "Production-grade CLI tool",
11 }
12}
Best Practices and Common Pitfalls
- Start Simple: Begin with minimal structure (cmd, internal, pkg) and expand as needed
- Avoid Over-Engineering: Don’t create directories until you actually need them
- Use
/internalWisely: Place business logic here to prevent unwanted imports - Keep
/pkgPublic: Only export what others need to consume - Testing Strategy:
- Put
TestMainin package directories - Use
/testfor integration tests requiring multiple packages
- Put
Common Mistakes:
- Placing business logic in
/cmd(should be in/internal) - Making
/pkga dumping ground for unrelated components - Ignoring Go’s package naming conventions
When Not to Use:
- Tiny single-binary projects
- Short-lived prototypes
- When your team has established different conventions
Conclusion
The golang-standards/project-layout provides a valuable starting point for Go projects needing structure at scale. While not mandatory, its patterns solve real organizational challenges in production systems.
Adopt this layout when:
- Building long-lived applications
- Developing shared libraries
- Working with teams that need clear code boundaries
- Integrating complex systems (CI/CD, multiple binaries)
For smaller projects, consider simplifying the structure while maintaining key principles like separation of concerns and package visibility.
Resources: