Skip to content

Example CLAUDE.md — Go Microservice

This example shows a CLAUDE.md for a Go HTTP microservice using the standard library for routing, sqlc for database access, and Docker for local development. It covers Go-specific conventions that keep Claude Code generating idiomatic code.

The CLAUDE.md File

# Project: Acme Notification Service

Go 1.22 HTTP microservice. Handles email and webhook notifications. PostgreSQL for storage, Redis for queuing.

## Commands

- `go run ./cmd/server` — start the server (port 8080)
- `go test ./...` — run all tests
- `go test -run TestSendEmail ./internal/email/` — single test
- `go test -race ./...` — run tests with race detector
- `golangci-lint run` — lint check
- `sqlc generate` — regenerate Go code from SQL queries
- `docker compose up -d` — start Postgres + Redis locally
- `make migrate-up` — apply database migrations

Always run `sqlc generate` after editing files in `sql/queries/`.

## Architecture

- `cmd/server/` — main entrypoint, server setup, graceful shutdown
- `internal/api/` — HTTP handlers and middleware
- `internal/email/` — email sending logic (SMTP + templates)
- `internal/webhook/` — outbound webhook delivery with retries
- `internal/queue/` — Redis-backed job queue
- `internal/store/` — database queries (generated by sqlc, do not edit manually)
- `sql/queries/` — SQL query files for sqlc
- `sql/migrations/` — migration files (golang-migrate format)

## Code Conventions

- Follow standard Go project layout — `cmd/` for entrypoints, `internal/` for private packages
- Use `context.Context` as the first parameter in all functions that do I/O
- Return errors, do not panic — `return fmt.Errorf("send email: %w", err)`
- Wrap errors with context using `%w` for unwrapping
- No global state — pass dependencies through structs
- Use interfaces for external dependencies (SMTP client, database) to enable testing

## Error Handling

```go
// Good: wrap with context
if err := store.CreateUser(ctx, user); err != nil {
    return fmt.Errorf("create user %s: %w", user.Email, err)
}

// Bad: bare return
if err := store.CreateUser(ctx, user); err != nil {
    return err
}

Database (sqlc)

  • Write SQL in sql/queries/*.sql with sqlc annotations
  • Run sqlc generate to produce Go code in internal/store/
  • Never edit files in internal/store/ manually — they are overwritten by sqlc
  • Migrations use golang-migrate — one up/down pair per change
  • Use transactions for multi-step operations: store.WithTx(ctx, func(q *Queries) error { ... })

HTTP Handlers

  • Handlers live in internal/api/ and follow this signature: func (h *Handler) methodName(w http.ResponseWriter, r *http.Request)
  • Use encoding/json for request/response marshaling
  • Validate input at the handler level before calling business logic
  • Return structured JSON errors: {"error": "message", "code": "INVALID_INPUT"}
  • Middleware chain: logging → recovery → auth → rate-limit → handler

Testing

  • Table-driven tests for all business logic
  • Use httptest for handler tests
  • Mock external services with interfaces — no mock frameworks needed
  • Test files alongside source: email.go → email_test.go
  • Integration tests tagged //go:build integration — run separately in CI

Git

  • Conventional commits: feat:, fix:, chore:, refactor:
  • Keep PRs focused — one concern per PR
  • Run golangci-lint run and go test -race ./... before pushing

Do NOT

  • Do not use third-party HTTP routers (chi, gin, echo) — use net/http
  • Do not edit generated files in internal/store/
  • Do not use init() functions
  • Do not use package-level variables for state
  • Do not use interface{} — use any or define proper types ```

Key Sections Explained

Code Conventions — Go has strong opinions and Claude needs explicit guidance to stay idiomatic. The error wrapping pattern and dependency injection rules prevent the most common non-idiomatic Go that Claude might generate.

Database (sqlc) — The critical rule is "never edit generated files." Without this, Claude will modify sqlc output directly and the changes will be overwritten on the next generate.

HTTP Handlers — Defines the exact handler signature and middleware order so Claude generates consistent handlers that integrate cleanly with the existing codebase.

Do NOT — Prevents Claude from adding routers, editing generated code, or using patterns the team has explicitly rejected.

See Also