Web Frameworks Best Practices
A condensed summary of the 25 most important web framework practices drawn from every page in this section.
Search across all documentation pages
A condensed summary of the 25 most important web framework practices drawn from every page in this section.
Keep handlers thin: Decode transport input, call services, map errors to HTTP - business rules live outside framework context types.
Prefer http.Handler at the core: Write portable handlers when possible so chi, mux, and framework adapters stay interchangeable.
Choose routers before frameworks: Reach for stdlib or chi until JSON binding volume justifies Gin or Echo lock-in.
Configure http.Server timeouts: Router middleware is not a substitute for ReadHeaderTimeout, ReadTimeout, and WriteTimeout on the server.
Register recovery middleware: Panic recovery belongs in the outer middleware stack on every public listener.
Order middleware deliberately: Logging and tracing outermost, auth next, timeout and body limits before handlers that may block.
Use route groups for API versions: Prefix /api/v1 with group-level middleware instead of repeating paths per handler.
Parse path params safely: URL params are strings; validate with strconv or binding before domain lookups.
Prefer ShouldBind* in Gin: Avoid BindJSON when you need custom error JSON - ShouldBindJSON does not write responses automatically.
Wire validators explicitly in Echo: Set e.Validator before relying on c.Validate - validation is not automatic out of the box.
Return errors centrally in Echo: Use handler error returns and a custom HTTPErrorHandler instead of scattering status writes.
Set Gin release mode in production: Use gin.New() with chosen middleware and gin.SetMode(gin.ReleaseMode) to avoid debug overhead.
Limit request bodies: Apply http.MaxBytesReader or framework equivalents before decoding large JSON payloads.
Propagate r.Context(): Pass request context into database and RPC calls for cancellation and deadlines.
Use typed context keys: Avoid string context keys for request-scoped values; use unexported custom types to prevent collisions.
Document host and proxy behavior: When using mux host matchers or subdomain routing, normalize Host/X-Forwarded-Host behind load balancers.
Mount migrations with prefixes: Run old and new routers side by side (/v1, /v2) until metrics show clean cutover.
Avoid double middleware: When mounting routers, apply logging and auth once at the outermost handler.
Test portable handlers with httptest: Exercise http.HandlerFunc directly without booting framework engines for unit tests.
Map validation errors for clients: Translate validator failures into field-scoped problem JSON instead of raw err.Error() strings.
Use DTO structs at the edge: Keep transport structs separate from domain models to prevent JSON tags leaking inward.
Pick mux only for matchers: Choose gorilla/mux when host/regex/header rules are required, not for ordinary CRUD convenience.
Authenticate WebSocket upgrades early: Validate credentials during the upgrade request before switching protocols.
Record framework choice in ADRs: Document scenario, ranked alternatives, and migration adapters when standardizing on a library.
Keep observability stdlib-shaped: Prefer http.Handler OTel middleware or verified framework contrib adapters for consistent traces.
No - stdlib plus chi covers many production APIs; frameworks mainly reduce JSON and routing ceremony.
Thin handlers that call plain Go services - it improves tests and eases router migration.
Checklist: server timeouts, recovery middleware, context propagation, and no business logic in gin.Context beyond binding.
When adding WebSockets, multi-tenant hosts, or OpenAPI codegen - requirements may outgrow the original pick.
Public APIs need stricter error mapping, body limits, and TLS termination discipline; internal APIs still need timeouts and context cancellation.
Frameworks sit on net/http - server configuration, client pooling, and middleware concepts from net/http still apply.
Only with shared observability contracts and portable handler guidelines; otherwise standardize on chi or one full framework.
Rewriting working handlers to framework contexts without a strangler mount plan - prefer adapters and prefixes first.
As many as you can diagram on one page - if order is unclear, consolidate or document the standard stack in main.
Keep JSON encoding in handlers or small response helpers; share schema types via DTO packages, not global gin.H maps.
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).
Reviewed by Chris St. John·Last updated Jul 18, 2026