| name | golang-project-structure |
| description | Go Standard Project Layout: /cmd, /internal, /pkg conventions, import organization, goimports, circular dependency prevention, and module boundaries. Use when scaffolding or reviewing Go project structure.
|
| allowed-tools | Bash(go list *), Bash(go mod *), Bash(goda *), Bash(goimports *), Bash(gofmt *), Read, Grep, Glob |
Go Standard Project Layout
A well-structured Go project makes the architecture self-documenting and prevents
a class of coupling bugs before they happen.
1. Standard Layout
myproject/
├── cmd/ # Entry points (main packages only)
│ ├── server/
│ │ └── main.go # go build ./cmd/server
│ └── worker/
│ └── main.go # go build ./cmd/worker
│
├── internal/ # Private code — cannot be imported by external modules
│ ├── user/ # Domain: user
│ │ ├── service.go
│ │ ├── service_test.go
│ │ ├── repository.go # interface definition
│ │ └── mocks/
│ ├── order/
│ └── platform/ # Cross-cutting: DB, HTTP, cache
│ ├── database/
│ ├── httpserver/
│ └── cache/
│
├── pkg/ # Public reusable packages (use sparingly)
│ └── validator/ # Only if external projects will import this
│
├── api/ # API contracts: .proto, OpenAPI, JSON schema
│ └── openapi.yaml
│
├── configs/ # Config templates (not secrets)
│ └── config.yaml.example
│
├── scripts/ # Build/analysis/migration scripts
│
├── go.mod
├── go.sum
└── README.md
2. Rules per Directory
/cmd — only main packages
package main
func main() {
cfg := config.Load()
db := platform.NewDB(cfg.Database)
svc := user.NewService(db)
srv := httpserver.New(cfg.HTTP, svc)
srv.Run()
}
func main() {
rows, err := db.Query("SELECT * FROM users")
...
}
/internal — the most important directory
- Enforced by the Go compiler: packages in /internal can only be imported
by code rooted at the parent of /internal.
- Put ALL business logic, domain models, and implementation details here.
- Prefer internal/ over pkg/ by default.
import "github.com/myorg/myproject/internal/user"
/pkg — only truly public, stable APIs
Use /pkg ONLY when:
- Other repositories will import this package
- The API is stable and versioned
- It has no knowledge of the business domain
3. Import Organization (goimports)
Three groups, separated by blank lines:
import (
"context"
"fmt"
"net/http"
"github.com/gin-gonic/gin"
"go.uber.org/zap"
"github.com/myorg/myproject/internal/user"
"github.com/myorg/myproject/pkg/validator"
)
goimports -l ./...
goimports -w ./...
go install golang.org/x/tools/cmd/goimports@latest
4. Circular Dependency Prevention
Go forbids circular imports at compile time. Architecture must be layered:
cmd → internal/domain → internal/platform → stdlib
Dependency direction rules:
cmd imports internal/* — allowed
internal/domain imports internal/platform — allowed
internal/platform imports internal/domain — FORBIDDEN (cycle)
- Two domain packages import each other — FORBIDDEN (cycle)
Breaking cycles: Interface Injection
type OrderQuerier interface {
GetRecentOrders(ctx context.Context, userID string) ([]Order, error)
}
type Service struct {
orders OrderQuerier
}
Detection
go build ./... 2>&1 | grep -i "import cycle"
goda tree ./...:all
go list -json ./internal/user/... | jq '.Imports[]'
go list -json ./... | jq 'select(.Imports[]? == "github.com/myorg/myproject/internal/user") | .ImportPath'
5. Package Naming Rules
✅ user, order, payment, cache, database
✅ httpserver, grpcserver (descriptive compound)
✅ validator, formatter, parser
❌ util, utils, common, helpers, misc, shared
❌ userutil, orderhelper (redundant suffix)
❌ UserService (exported name as package — use user.Service)
❌ user_service (underscore — only in test files)
Rule: name a package by what it provides, not what it contains.
6. Verification Commands
go list ./...
goda tree ./...:all
go build ./... 2>&1 | grep -i cycle
goimports -l ./...
go list ./... | grep -E "util|common|helper|misc|shared"