ADR 0004 — Four lifecycle hooks instead of a middleware chain
- Status: Accepted
- Date: 2025-05
Context
An earlier framework used Hono’s per-path .use() middleware: a registration
step mounted middleware by scope, and request behaviour was assembled from a
chain of middleware functions. That chain is a framework-specific construct —
adopting it means adopting the framework (see ADR 0001).
stitchkit still needs the cross-cutting behaviour middleware usually provides: logging, auth, response shaping, error formatting.
Decision
Four lifecycle hooks, plain functions, no chain:
onRequest(req)— runs first; logging, rate limiting. May short-circuit with aResponse.beforeHandle(ctx, endpoint)— auth, scope checks. Runs after the context is built, before the handler.afterHandle(ctx, result, endpoint)— response transform, cache headers, audit logging. May replace the result.onError(ctx, error, endpoint)— error formatting, alerting.
Route groups (ADR 0006) may carry their own hooks. Execution order is global hook → group hook → handler.
Consequences
- Hooks are ordinary functions with zero framework dependency — the opposite of middleware lock-in.
- The four hooks cover every cross-cutting concern the source projects needed.
- There is no composable middleware pipeline — hooks are flat, not chained. Behaviour that a middleware stack would compose is instead expressed as plain code inside a hook. For stitchkit’s scope this is simpler, not weaker.