← All Templates

Go API Server

Rules for building Go HTTP API servers with testing best practices and Docker deployment.

go api go-test chi docker

v1.0.0 · Updated Apr 9, 2026

CLAUDE.md — Save as CLAUDE.md in your project root.

Project Setup

Suggest Change
- Initialize with `go mod init` using full module path
- Structure: `cmd/server/main.go` entry point, `internal/` for private packages, `pkg/` only for truly reusable code
- Use `internal/handler/` for HTTP handlers, `internal/service/` for business logic, `internal/model/` for types
- Run npm run build as `go build ./...` to verify compilation after changes
- Use Read tool to check existing code before modifying; understand interfaces first
- Keep `go.sum` committed; run `go mod tidy` after dependency changes
- No global state; pass dependencies via constructor injection

Testing Strategy

Suggest Change
- Follow RED-GREEN-REFACTOR: write failing test, implement, refactor
- Run tests with npm test as `go test -v -count=1 ./...`
- Use `-count=1` to disable test caching during development
- Use `httptest.NewRecorder()` and `httptest.NewRequest()` for handler tests
- Table-driven tests for multiple cases: `tests := []struct{ name string; ... }{}`
- Use `t.Helper()` in test helper functions for correct line reporting
- Use `t.Parallel()` for independent tests to speed up test suite
- Test at the handler level primarily; unit test complex business logic separately
- Use `t.TempDir()` for filesystem tests; it auto-cleans

API Patterns

Suggest Change
- Use `chi.NewRouter()` for routing; group related endpoints with `r.Route()`
- Return JSON with `application/json` content type; use a `respond` helper
- Parse request bodies with `json.NewDecoder(r.Body).Decode(&v)`; limit body size with `http.MaxBytesReader`
- Use middleware for cross-cutting concerns: logging, CORS, auth, request ID
- URL parameters via `chi.URLParam(r, "id")`; query params via `r.URL.Query().Get("key")`
- Pagination: accept `?page=1&limit=20`, default limit to 20, cap at 100
- API versioning via URL prefix: `/api/v1/...`
- Always set `Content-Type` header before writing response body
- Use `context.Context` for request-scoped values and cancellation

Error Handling

Suggest Change
- Define domain errors as package-level variables: `var ErrNotFound = errors.New("not found")`
- Map domain errors to HTTP status codes in handlers, not in business logic
- Use `errors.Is()` and `errors.As()` for error checking; never string-compare errors
- Return structured error responses: `{"error": "message", "code": "NOT_FOUND"}`
- Log errors with context: `slog.Error("query failed", "err", err, "id", id)`
- Use `defer` for cleanup; check error return of `Close()` calls
- Wrap errors with `fmt.Errorf("operation: %w", err)` to build error chains
- Recover from panics only in middleware; let them crash in development

Deployment

Suggest Change
- Multi-stage Dockerfile: build with `golang:1.22-alpine`, run with `alpine:3.19`
- Copy only the binary; use `CGO_ENABLED=0` for static linking
- Expose health check endpoint at `GET /healthz` returning 200
- Use Bash to run `docker build -t app . && docker run -p 8080:8080 app`
- Configure via environment variables; use `os.Getenv()` with sensible defaults
- Graceful shutdown: listen for `SIGTERM`, drain connections with `srv.Shutdown(ctx)`
- Set read/write timeouts on `http.Server`: `ReadTimeout: 5s, WriteTimeout: 10s`
- Never log secrets; sanitize request logs to exclude Authorization headers