Files
httpserver/docs/adr/ADR-003-no-default-middleware.md
Rene Nochebuena 1ec0780f72 docs(httpserver): correct tier from 4 to 3
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.
2026-03-19 13:39:19 +00:00

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 RequestLogger needs a logger argument that httpserver would have to accept on behalf of httpmw. 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 the BeforeStart hook (before OnStart binds the port).
  • The WithMiddleware option appends, so multiple calls accumulate: WithMiddleware(a, b) followed by WithMiddleware(c) results in [a, b, c] applied in that order.