httpserver depends on launcher (Tier 2), placing it at Tier 3. With launcher corrected from Tier 5 to Tier 2, httpserver's tier drops accordingly.
2.6 KiB
ADR-003: No Middleware Installed by Default
Status: Accepted Date: 2026-03-18
Context
Many HTTP frameworks and server libraries install a default middleware stack — request logging, panic recovery, CORS headers, request ID injection — on the assumption that most applications will want these. The question is whether httpserver.New(...) should do the same.
Arguments for a default stack:
- Reduces boilerplate for the common case.
- Ensures recovery from panics is always in place.
Arguments against:
- Different applications need different middleware in different orders. Order matters: request ID must precede the logger so the logger can attach the ID; auth must precede RBAC; compression must follow auth.
- Installing middleware that the caller neither requested nor knows about makes the system harder to reason about and test.
- The micro-lib design principle is zero-surprise construction:
New()produces a minimal, predictable value. Behavior is added explicitly. - A middleware like
RequestLoggerneeds a logger argument thathttpserverwould have to accept on behalf ofhttpmw. This couples configuration surface areas that should be independent.
Decision
httpserver.New(...) installs no middleware. The router returned from New() is a bare chi.NewRouter(). Middleware is composed explicitly by the caller using WithMiddleware(...):
srv := httpserver.New(logger, cfg,
httpserver.WithMiddleware(
httpmw.RequestID(uuid.NewString),
httpmw.Recover(),
httpmw.RequestLogger(&logAdapter{logger}),
),
)
WithMiddleware is a variadic functional option that appends middleware to an internal slice. In OnInit(), each middleware is applied to the router via s.Router.Use(mw) in registration order (first registered = outermost in the chain).
Consequences
- Every application must explicitly compose its middleware stack. This is intentional: the stack is visible, ordered, and testable in the application source.
- Panic recovery is not automatic. If a caller omits
httpmw.Recover(), unhandled panics will crash the process. This is a deliberate trade-off — it makes the stack explicit and avoids silent behavior. - Adding middleware after
OnInit()has run is permitted by chi but is not the intended usage pattern. Route registration and middleware composition should both happen in theBeforeStarthook (beforeOnStartbinds the port). - The
WithMiddlewareoption appends, so multiple calls accumulate:WithMiddleware(a, b)followed byWithMiddleware(c)results in[a, b, c]applied in that order.