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