Go HTTP Server (net/http + chi)
Production HTTP services with chi router, middleware, and graceful shutdown.
# CLAUDE.md — Go HTTP Server (net/http + chi)
## Stack
- `net/http` for the server, **chi** for routing (or `http.ServeMux` from Go 1.22+ if your routing is simple).
- One handler function per route. Handlers are `func(w http.ResponseWriter, r *http.Request)`.
- Don't build a custom HTTP framework. Compose middleware functions.
## Server bootstrap
```go
srv := &http.Server{
Addr: ":" + port,
Handler: routes(),
ReadHeaderTimeout: 5 * time.Second,
ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 120 * time.Second,
BaseContext: func(net.Listener) context.Context { return appCtx },
}
```
- Always set timeouts. The defaults are unbounded.
- `BaseContext` carries app-level cancellation into every request.
## Graceful shutdown
```go
go func() {
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Fatal(err)
}
}()
<-ctx.Done()
shutdownCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
srv.Shutdown(shutdownCtx)
```
- Listen for `SIGTERM` / `SIGINT`, cancel `ctx`, call `Shutdown` with a timeout.
- 30s grace is reasonable for human-facing services. Long-running streams need more or you cut them off.
## Routing with chi
- Subrouters per resource: `r.Route("/users", func(r chi.Router) { ... })`.
- Middleware is a `func(http.Handler) http.Handler` chain. Order: recovery → logging → request id → auth → routes.
- Use chi's request id, logger, and recoverer middleware before writing your own.
## Request handling
- Parse JSON with `json.NewDecoder(r.Body).Decode(&req)` and `defer r.Body.Close()` (for large bodies).
- Validate after decoding. Return 400 with a JSON error on failure.
- Set a max body size: `r.Body = http.MaxBytesReader(w, r.Body, 1<<20)` (1 MB) at the edge.
## Responses
- One helper per response shape:
```go
func writeJSON(w http.ResponseWriter, status int, v any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
_ = json.NewEncoder(w).Encode(v)
}
```
- Set `Content-Type` before `WriteHeader`. After `WriteHeader`, headers are frozen.
- Don't write the response twice. After an error path, `return`.
## Errors
- Map domain errors to HTTP status in one place (the writer or a helper):
```go
switch {
case errors.Is(err, ErrNotFound): http.Error(w, "not found", 404)
case errors.As(err, &v): http.Error(w, v.Message, 400)
default: http.Error(w, "internal", 500)
}
```
- Log the underlying error server-side. Don't echo it to clients.
## Auth
- Middleware that extracts a user from the request, attaches to context with a typed key, and rejects unauthenticated.
- Avoid `context.Value` sprawl. Encapsulate access: `userFromContext(r.Context()) (User, bool)`.
## Don't
- Don't write to `http.ResponseWriter` after the handler returns — it's gone.
- Don't ignore `r.Context()`. Pass it to every downstream call (DB, HTTP) so cancellation propagates.
- Don't use `init()` to register routes. Build them explicitly.
- Don't share `http.Client` instances with default settings — set timeouts.
Other Go templates
Idiomatic Go Rules
Effective Go: error handling, interfaces, goroutines, project layout.
Go + sqlx + Postgres
Database access with sqlx, pgx, and migration discipline.
Go gRPC Services
gRPC + protobuf: service definitions, interceptors, streaming, errors.
Go Testing Rules
Table-driven tests, subtests, testify, integration tests, coverage.