dotmd
// 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
    CLAUDE.md
// File Content
CLAUDE.md
1# CLAUDE.md — Go Microservice
2
3This file contains your project instructions for Claude Code. Apply these
4coding conventions, architecture decisions, and workflow preferences to every
5change you make in this repository.
6
7---
8
9HTTP 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.
10
11## Quick Reference
12
13| 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` |
30
31Pick the migration tool that already exists in `go.mod`. If none, prefer goose.
32
33## Project Structure
34
35```
36├── cmd/
37│ └── server/ # main.go — wires everything, starts HTTP
38├── internal/
39│ ├── config/ # env parsing (envconfig / viper / koanf)
40│ ├── handler/ # HTTP handlers — thin, call services
41│ ├── middleware/ # auth, logging, recovery, request-id
42│ ├── service/ # business logic — no HTTP concepts
43│ ├── repository/ # data access — one file per aggregate
44│ ├── model/ # domain types, value objects
45│ └── platform/ # infra wiring: DB, cache, messaging clients
46├── migrations/ # SQL migration files (sequential numbered)
47├── pkg/ # Exported utilities (only if truly reusable)
48├── api/ # OpenAPI specs, proto files
49├── scripts/ # Dev and CI helper scripts
50├── .golangci.yml # Linter config
51├── docker-compose.yml
52├── Dockerfile
53└── Makefile
54```
55
56`internal/` is the default. Only move to `pkg/` when another service imports it. Handler → Service → Repository is the dependency direction. Never skip layers.
57
58## Code Conventions
59
60### Errors
61
62- 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 ```go
65 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.
73
74### context.Context
75
76- 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.
81
82### Logging
83
84- 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.
89
90### Configuration & Environment
91
92- 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.
96
97### Dependency Injection
98
99- Constructor injection, no framework. Wire in `cmd/server/main.go`:
100 ```go
101 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.
107
108### HTTP Handlers
109
110- 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 ```json
115 {"error": {"code": "not_found", "message": "order 123 not found"}}
116 ```
117- Middleware order: recovery → request-id → logging → auth → rate-limit.
118
119### Database
120
121- 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 ```go
125 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()`.
135
136### Timeouts & Graceful Shutdown
137
138- `http.Server.ReadTimeout`: 5s. `WriteTimeout`: 10s. `IdleTimeout`: 120s.
139- Graceful shutdown in `main()`:
140 ```go
141 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 ```
149
150## Testing Strategy
151
152### Unit Tests
153
154- Colocate with source: `order_service.go` → `order_service_test.go` in the same package.
155- Table-driven tests with `t.Run` subtests:
156 ```go
157 tests := []struct {
158 name string
159 input PlaceOrderReq
160 wantErr error
161 }{
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.
174
175### Integration Tests
176
177- Guard with build tag: `//go:build integration`.
178- Use `testcontainers-go` for Postgres, Redis, etc.:
179 ```go
180 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.
191
192### Test Naming
193
194- Functions: `Test<Type>_<Method>_<scenario>` — e.g. `TestOrderService_PlaceOrder_EmptyItems`.
195- Files: `<source>_test.go`.
196
197### What to Test
198
199- 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()`.
203
204## When Using Claude Code
205
206### Exploring the Codebase
207
208Before making changes, orient yourself:
209
210```
211# Find the router setup
212rg "NewRouter\|NewMux\|gin.New\|echo.New\|chi.NewRouter" cmd/ internal/
213
214# Find where an entity is defined
215rg "type Order struct" internal/
216
217# Check existing patterns for a new handler
218cat internal/handler/order.go
219
220# See what interfaces a service depends on
221rg "type.*interface" internal/service/
222
223# Check migration history
224ls -la migrations/
225```
226
227### Making Changes
228
2291. **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 ```bash
233 go vet ./...
234 golangci-lint run ./...
235 go test ./... -race -count=1
236 ```
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.
239
240### When to Ask vs. Proceed
241
242**Just do it:**
243- Adding a new handler that follows an existing pattern
244- Writing tests for existing code
245- Fixing lint errors
246- Adding error wrapping
247- Creating a migration for a new table
248
249**Ask first:**
250- Changing the router, logger, or DB driver
251- Adding a new dependency to `go.mod`
252- Modifying middleware that affects all routes
253- Changing the project structure or package layout
254- Anything that touches auth/authz logic
255
256### Git Workflow
257
258- 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 ```bash
262 go mod tidy
263 go vet ./...
264 golangci-lint run ./...
265 go test ./... -race -count=1
266 ```
267- If tests fail, fix them before committing. Do not skip or comment out tests.
268
269### Common Pitfalls
270
271- **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