// Config Record
>Go Microservice
Claude Code instructions for Go microservices covering layering, context propagation, and reliability patterns.
author:
dotmd Team
license:CC0
published:Feb 19, 2026
// Installation
>Add this file to your project repository:
- Claude Code--path=
CLAUDE.md
// File Content
CLAUDE.md
1# CLAUDE.md — Go Microservice23This file contains your project instructions for Claude Code. Apply these4coding conventions, architecture decisions, and workflow preferences to every5change you make in this repository.67---89HTTP service in Go. Prioritize small interfaces, explicit error handling, and production-safe defaults. Every change must pass `go vet`, `golangci-lint run`, and `go test ./...` before committing.1011## Quick Reference1213| Task | Command |14|---|---|15| Run service | `go run ./cmd/server` |16| Run all tests | `go test ./... -race -count=1` |17| Run single package tests | `go test ./internal/order/... -v` |18| Integration tests only | `go test ./... -tags=integration -race` |19| Lint | `golangci-lint run ./...` |20| Vet | `go vet ./...` |21| Build binary | `go build -o bin/server ./cmd/server` |22| Generate (mocks, sqlc, etc.) | `go generate ./...` |23| Tidy deps | `go mod tidy` |24| Docker up (full stack) | `docker compose up -d` |25| Docker rebuild | `docker compose up -d --build` |26| Migrate up (goose) | `goose -dir migrations postgres "$DATABASE_URL" up` |27| Migrate create (goose) | `goose -dir migrations create <name> sql` |28| Migrate up (atlas) | `atlas migrate apply --url "$DATABASE_URL"` |29| Migrate up (golang-migrate) | `migrate -path migrations -database "$DATABASE_URL" up` |3031Pick the migration tool that already exists in `go.mod`. If none, prefer goose.3233## Project Structure3435```36├── cmd/37│ └── server/ # main.go — wires everything, starts HTTP38├── internal/39│ ├── config/ # env parsing (envconfig / viper / koanf)40│ ├── handler/ # HTTP handlers — thin, call services41│ ├── middleware/ # auth, logging, recovery, request-id42│ ├── service/ # business logic — no HTTP concepts43│ ├── repository/ # data access — one file per aggregate44│ ├── model/ # domain types, value objects45│ └── platform/ # infra wiring: DB, cache, messaging clients46├── migrations/ # SQL migration files (sequential numbered)47├── pkg/ # Exported utilities (only if truly reusable)48├── api/ # OpenAPI specs, proto files49├── scripts/ # Dev and CI helper scripts50├── .golangci.yml # Linter config51├── docker-compose.yml52├── Dockerfile53└── Makefile54```5556`internal/` is the default. Only move to `pkg/` when another service imports it. Handler → Service → Repository is the dependency direction. Never skip layers.5758## Code Conventions5960### Errors6162- Wrap errors with `fmt.Errorf("operation context: %w", err)` — always use `%w` for wrapping.63- Define domain errors as sentinel values in `internal/model/errors.go`:64 ```go65 var (66 ErrNotFound = errors.New("not found")67 ErrConflict = errors.New("conflict")68 ErrUnauthorized = errors.New("unauthorized")69 )70 ```71- Handlers map domain errors to HTTP status codes. Services never import `net/http`.72- Check with `errors.Is()` and `errors.As()`, never compare strings.7374### context.Context7576- First parameter to every exported function: `ctx context.Context`.77- Pass context through the entire call chain: handler → service → repository → DB query.78- Attach request-scoped values (request ID, user ID, trace ID) via middleware.79- Never store `context.Context` in a struct.80- Use `context.WithTimeout` for outbound calls (HTTP, DB, gRPC) — 5s default for DB, 10s for external APIs.8182### Logging8384- Use structured logging: `log/slog` (stdlib, Go 1.21+). Fall back to `zerolog` or `zap` if already in `go.mod`.85- Always log with context: `slog.InfoContext(ctx, "order created", "order_id", id)`.86- Log at handler entry/exit and on errors. Do not log in hot loops.87- Levels: `Debug` for dev tracing, `Info` for business events, `Warn` for recoverable issues, `Error` for failures requiring attention.88- Never log credentials, tokens, PII, or full request bodies.8990### Configuration & Environment9192- All config via environment variables. Parse in `internal/config/` at startup.93- Use `envconfig`, `koanf`, or `viper` — whichever is in `go.mod`. If none, use `envconfig`.94- Fail fast on missing required config: validate in `config.Load()`, return error, crash in `main()`.95- No globals for config. Pass config struct (or relevant subset) via constructor injection.9697### Dependency Injection9899- Constructor injection, no framework. Wire in `cmd/server/main.go`:100 ```go101 repo := repository.NewOrderRepo(db)102 svc := service.NewOrderService(repo, logger)103 h := handler.NewOrderHandler(svc, logger)104 ```105- Accept interfaces, return structs.106- Keep interfaces small — 1-3 methods. Define interfaces where they're used (in the consumer package), not where they're implemented.107108### HTTP Handlers109110- Detect the router in `go.mod` (chi / gin / echo / stdlib `net/http`) and follow its patterns.111- Handlers decode request → call service → encode response. No business logic.112- Always set `Content-Type: application/json` for JSON responses.113- Return consistent error envelope:114 ```json115 {"error": {"code": "not_found", "message": "order 123 not found"}}116 ```117- Middleware order: recovery → request-id → logging → auth → rate-limit.118119### Database120121- Use `pgx` (preferred) or `database/sql` for Postgres. Use `sqlc` for type-safe SQL if present.122- One `*pgxpool.Pool` created in `main()`, passed down.123- Transactions live in the service layer:124 ```go125 func (s *OrderService) PlaceOrder(ctx context.Context, req PlaceOrderReq) error {126 tx, err := s.pool.Begin(ctx)127 if err != nil { return fmt.Errorf("begin tx: %w", err) }128 defer tx.Rollback(ctx)129 // ... repo calls using tx ...130 return tx.Commit(ctx)131 }132 ```133- Always use query parameters (`$1`, `$2`), never string concatenation.134- Close rows: `defer rows.Close()`.135136### Timeouts & Graceful Shutdown137138- `http.Server.ReadTimeout`: 5s. `WriteTimeout`: 10s. `IdleTimeout`: 120s.139- Graceful shutdown in `main()`:140 ```go141 ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)142 defer stop()143 // ... start server ...144 <-ctx.Done()145 shutdownCtx, cancel := context.WithTimeout(context.Background(), 15*time.Second)146 defer cancel()147 srv.Shutdown(shutdownCtx)148 ```149150## Testing Strategy151152### Unit Tests153154- Colocate with source: `order_service.go` → `order_service_test.go` in the same package.155- Table-driven tests with `t.Run` subtests:156 ```go157 tests := []struct {158 name string159 input PlaceOrderReq160 wantErr error161 }{162 {"valid order", PlaceOrderReq{...}, nil},163 {"empty items", PlaceOrderReq{}, ErrValidation},164 }165 for _, tt := range tests {166 t.Run(tt.name, func(t *testing.T) {167 err := svc.PlaceOrder(ctx, tt.input)168 assert.ErrorIs(t, err, tt.wantErr)169 })170 }171 ```172- Mock interfaces with hand-written mocks or `gomock`/`mockery` if already in use.173- Use `testify/assert` and `testify/require` for assertions — `require` for fatal, `assert` for non-fatal.174175### Integration Tests176177- Guard with build tag: `//go:build integration`.178- Use `testcontainers-go` for Postgres, Redis, etc.:179 ```go180 func TestOrderRepo_Integration(t *testing.T) {181 if testing.Short() { t.Skip("skipping integration test") }182 ctx := context.Background()183 pg, err := postgres.Run(ctx, "postgres:16-alpine")184 require.NoError(t, err)185 t.Cleanup(func() { pg.Terminate(ctx) })186 // ... run migrations, test repo methods ...187 }188 ```189- Run migrations against the test container before assertions.190- Each test gets a fresh DB or uses transactions that roll back.191192### Test Naming193194- Functions: `Test<Type>_<Method>_<scenario>` — e.g. `TestOrderService_PlaceOrder_EmptyItems`.195- Files: `<source>_test.go`.196197### What to Test198199- Services: all business rules, edge cases, error paths.200- Repositories: integration tests against real DB.201- Handlers: request decoding, status codes, error mapping. Use `httptest.NewRecorder()`.202- Skip testing: auto-generated code, simple struct definitions, `main()`.203204## When Using Claude Code205206### Exploring the Codebase207208Before making changes, orient yourself:209210```211# Find the router setup212rg "NewRouter\|NewMux\|gin.New\|echo.New\|chi.NewRouter" cmd/ internal/213214# Find where an entity is defined215rg "type Order struct" internal/216217# Check existing patterns for a new handler218cat internal/handler/order.go219220# See what interfaces a service depends on221rg "type.*interface" internal/service/222223# Check migration history224ls -la migrations/225```226227### Making Changes2282291. **Read first.** Before writing code, read the relevant handler, service, and repository files. Understand the existing pattern before replicating it.2302. **Follow existing patterns.** If the repo uses `chi`, don't introduce `gin`. If errors are wrapped with `fmt.Errorf`, don't switch to `pkg/errors`. Match what's there.2313. **Run checks after every change:**232 ```bash233 go vet ./...234 golangci-lint run ./...235 go test ./... -race -count=1236 ```2374. **One concern per commit.** Don't mix a refactor with a feature. Commit the migration separately from the handler.2385. **Generate after schema changes.** If you modify a `.sql` query file or proto, run `go generate ./...` and include generated files in the commit.239240### When to Ask vs. Proceed241242**Just do it:**243- Adding a new handler that follows an existing pattern244- Writing tests for existing code245- Fixing lint errors246- Adding error wrapping247- Creating a migration for a new table248249**Ask first:**250- Changing the router, logger, or DB driver251- Adding a new dependency to `go.mod`252- Modifying middleware that affects all routes253- Changing the project structure or package layout254- Anything that touches auth/authz logic255256### Git Workflow257258- Branch from `main`. Name: `feat/<thing>`, `fix/<thing>`, `chore/<thing>`.259- Write a clear commit message: imperative mood, reference ticket if exists.260- Before pushing, always run the full check suite:261 ```bash262 go mod tidy263 go vet ./...264 golangci-lint run ./...265 go test ./... -race -count=1266 ```267- If tests fail, fix them before committing. Do not skip or comment out tests.268269### Common Pitfalls270271- **Forgetting `-race` flag.** Always test with `-race`. Data races in Go are silent killers.272- **Nil pointer on zero-value structs.** Check for nil before dereferencing, especially from DB queries.273- **Goroutine leaks.** If you spawn a goroutine, ensure it has a shutdown path via context cancellation or channel close.274- **Import cycles.** If you hit one, you're probably putting interfaces in the wrong package. Move the interface to the consumer.275- **Exported types in `internal/`.** This is fine — `internal/` restricts external imports, not internal cross-package use.276