CORE CONCEPTS

Hooks

Hooks let you observe (or lightly react to) the request lifecycle without adding another layer to the middleware chain. They're a lightweight pub/sub registry, exposed on app.hooks.

Available Hooks

HookFiresReceives
beforeRequestOnce per request, before the middleware chain runs(ctx)
afterResponseAfter the Response is produced — both success and error paths(ctx, response)
onErrorWhen an unhandled error escapes the middleware chain(error, ctx)
Registering hooks
app.hooks.on('afterResponse', (ctx, response) => { metrics.record(ctx.request.method, response.status); }); app.hooks.on('onError', (error, ctx) => { errorTracker.capture(error, { url: ctx.request.url }); });

Hooks vs. Middleware

Reach for a hook when you're purely observing (metrics, logging, error tracking) and don't need to touch the request/response. Reach for middleware when you need to short-circuit the chain, mutate the request/response, or run code between specific other middleware.

Hooks also can't produce a Response — they're fire-and-forget observers, not part of the response pipeline.

Error Isolation

If a hook handler throws, Futon swallows the error and continues running the remaining handlers for that hook — a broken metrics call can't crash a request. Handle your own errors inside the hook if you need to know about failures.

Multiple handlers, one hook
app.hooks.on('afterResponse', (ctx, response) => { /* handler A */ }); app.hooks.on('afterResponse', (ctx, response) => { /* handler B still runs even if A throws */ });

Removing a Hook

off()
const handler = (ctx) => console.log('request started'); app.hooks.on('beforeRequest', handler); app.hooks.off('beforeRequest', handler);

Zero-cost When Unused

HookSystem.has(name) reports whether any handler is registered for a given hook. Futon's fetch() guards every emit() call with has() first, so apps that never register hooks skip the async dispatch overhead entirely.

Error Handling
Express Adapter