All Go templates

Go HTTP Server (net/http + chi)

Production HTTP services with chi router, middleware, and graceful shutdown.

DevZone Tools1,840 copiesUpdated Mar 11, 2026Go
# 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