Go Project Structure

Standard Go project layout following community conventions. Organize your Go projects for maintainability, testability, and clarity.

Use Case

Use this structure when you need to:

  • Start a new Go project
  • Organize a growing codebase
  • Follow Go community standards
  • Prepare for open source release

Standard Layout

 1myproject/
 2β”œβ”€β”€ cmd/                    # Main applications
 3β”‚   └── myapp/
 4β”‚       └── main.go
 5β”œβ”€β”€ internal/               # Private application code
 6β”‚   β”œβ”€β”€ app/
 7β”‚   β”œβ”€β”€ pkg/
 8β”‚   └── config/
 9β”œβ”€β”€ pkg/                    # Public library code
10β”‚   β”œβ”€β”€ api/
11β”‚   └── utils/
12β”œβ”€β”€ api/                    # API definitions (OpenAPI, Protocol Buffers)
13β”œβ”€β”€ web/                    # Web assets
14β”œβ”€β”€ configs/                # Configuration files
15β”œβ”€β”€ scripts/                # Build and deployment scripts
16β”œβ”€β”€ test/                   # Additional test data and helpers
17β”œβ”€β”€ docs/                   # Documentation
18β”œβ”€β”€ examples/               # Example code
19β”œβ”€β”€ tools/                  # Supporting tools
20β”œβ”€β”€ vendor/                 # Vendored dependencies (optional)
21β”œβ”€β”€ go.mod                  # Module definition
22β”œβ”€β”€ go.sum                  # Dependency checksums
23β”œβ”€β”€ Makefile                # Build automation
24└── README.md

Explanation

  • cmd/ - Main entry points, one subdirectory per executable
  • internal/ - Private code, cannot be imported by other projects
  • pkg/ - Public library code, can be imported by others
  • api/ - API contracts (protobuf, OpenAPI specs)
  • configs/ - Configuration file templates
  • test/ - Additional test files and test data

Examples

Example 1: Simple CLI Tool

 1mycli/
 2β”œβ”€β”€ cmd/
 3β”‚   └── mycli/
 4β”‚       └── main.go          # Entry point
 5β”œβ”€β”€ internal/
 6β”‚   β”œβ”€β”€ command/             # Command implementations
 7β”‚   β”‚   β”œβ”€β”€ init.go
 8β”‚   β”‚   β”œβ”€β”€ run.go
 9β”‚   β”‚   └── status.go
10β”‚   └── config/
11β”‚       └── config.go        # Configuration handling
12β”œβ”€β”€ go.mod
13β”œβ”€β”€ go.sum
14β”œβ”€β”€ Makefile
15└── README.md

main.go:

 1package main
 2
 3import (
 4    "fmt"
 5    "os"
 6    
 7    "github.com/user/mycli/internal/command"
 8)
 9
10func main() {
11    if err := command.Execute(); err != nil {
12        fmt.Fprintf(os.Stderr, "Error: %v\n", err)
13        os.Exit(1)
14    }
15}

Example 2: Web Service

 1myservice/
 2β”œβ”€β”€ cmd/
 3β”‚   └── server/
 4β”‚       └── main.go
 5β”œβ”€β”€ internal/
 6β”‚   β”œβ”€β”€ handler/             # HTTP handlers
 7β”‚   β”‚   β”œβ”€β”€ user.go
 8β”‚   β”‚   └── auth.go
 9β”‚   β”œβ”€β”€ service/             # Business logic
10β”‚   β”‚   └── user_service.go
11β”‚   β”œβ”€β”€ repository/          # Data access
12β”‚   β”‚   └── user_repo.go
13β”‚   └── model/               # Domain models
14β”‚       └── user.go
15β”œβ”€β”€ pkg/
16β”‚   └── client/              # Public API client
17β”‚       └── client.go
18β”œβ”€β”€ api/
19β”‚   └── openapi.yaml         # API specification
20β”œβ”€β”€ configs/
21β”‚   └── config.yaml
22β”œβ”€β”€ go.mod
23└── README.md

main.go:

 1package main
 2
 3import (
 4    "log"
 5    "net/http"
 6    
 7    "github.com/user/myservice/internal/handler"
 8    "github.com/user/myservice/internal/repository"
 9    "github.com/user/myservice/internal/service"
10)
11
12func main() {
13    // Initialize dependencies
14    repo := repository.NewUserRepository()
15    svc := service.NewUserService(repo)
16    h := handler.NewHandler(svc)
17    
18    // Setup routes
19    http.HandleFunc("/users", h.HandleUsers)
20    
21    // Start server
22    log.Println("Server starting on :8080")
23    log.Fatal(http.ListenAndServe(":8080", nil))
24}

Example 3: Library with Examples

 1mylib/
 2β”œβ”€β”€ pkg/
 3β”‚   └── mylib/
 4β”‚       β”œβ”€β”€ core.go
 5β”‚       β”œβ”€β”€ core_test.go
 6β”‚       β”œβ”€β”€ utils.go
 7β”‚       └── utils_test.go
 8β”œβ”€β”€ examples/
 9β”‚   β”œβ”€β”€ basic/
10β”‚   β”‚   └── main.go
11β”‚   └── advanced/
12β”‚       └── main.go
13β”œβ”€β”€ docs/
14β”‚   β”œβ”€β”€ getting-started.md
15β”‚   └── api.md
16β”œβ”€β”€ go.mod
17β”œβ”€β”€ go.sum
18└── README.md

Example 4: Makefile

 1.PHONY: build test clean run
 2
 3# Build binary
 4build:
 5	go build -o bin/myapp cmd/myapp/main.go
 6
 7# Run tests
 8test:
 9	go test -v ./...
10
11# Run tests with coverage
12test-coverage:
13	go test -v -coverprofile=coverage.out ./...
14	go tool cover -html=coverage.out
15
16# Run linter
17lint:
18	golangci-lint run
19
20# Format code
21fmt:
22	go fmt ./...
23
24# Tidy dependencies
25tidy:
26	go mod tidy
27
28# Run application
29run:
30	go run cmd/myapp/main.go
31
32# Clean build artifacts
33clean:
34	rm -rf bin/
35	rm -f coverage.out
36
37# Install dependencies
38deps:
39	go mod download
40
41# Build for multiple platforms
42build-all:
43	GOOS=linux GOARCH=amd64 go build -o bin/myapp-linux-amd64 cmd/myapp/main.go
44	GOOS=darwin GOARCH=amd64 go build -o bin/myapp-darwin-amd64 cmd/myapp/main.go
45	GOOS=windows GOARCH=amd64 go build -o bin/myapp-windows-amd64.exe cmd/myapp/main.go

Notes

  • Use internal/ to prevent external imports of private code
  • Keep pkg/ for genuinely reusable code
  • One main.go per executable in cmd/
  • Follow Go naming conventions (lowercase packages)
  • Use go mod for dependency management

Gotchas/Warnings

  • ⚠️ internal/: Cannot be imported from outside the project
  • ⚠️ pkg/: Only put stable, public APIs here
  • ⚠️ Flat structure: Small projects can stay flat - don't over-engineer
  • ⚠️ Vendor: Only vendor if you have specific requirements
comments powered by Disqus