Request Hooks

onRequest, preHandler, preValidation, onSend

Request Hooks

Per-request hooks run at each step of the lifecycle — authentication, parsing, response shaping all live here.

5 min read Level 3/5 #fastify#hooks#lifecycle
What you'll learn
  • Register an onRequest hook for early checks
  • Use preHandler for authorization
  • Use onSend to mutate the outgoing payload

A request flows through a fixed series of stages. At each stage you can run a hook to inspect, mutate, or short-circuit the request.

The Order

  1. onRequest — earliest; no body parsed yet, ideal for rate limits and quick rejects
  2. preParsing — wrap the body stream (rare)
  3. preValidation — body present, schema not yet enforced
  4. preHandler — body validated, run authorization here
  5. handler — your route
  6. preSerialization — mutate the value before it becomes JSON
  7. onSend — modify the serialized payload or headers
  8. onResponse — request finished; logging and metrics

Auth in preHandler

app.addHook('preHandler', async (req, reply) => {
  const token = req.headers.authorization?.replace('Bearer ', '');
  if (!token) throw app.httpErrors.unauthorized();

  const user = await verifyToken(token);
  req.user = user;
});

Throwing inside any hook short-circuits the request — the handler never runs, and the error flows to your error handler.

Per-Route Hooks

app.post('/admin/wipe', {
  preHandler: requireAdmin,
  handler: async () => wipeDatabase(),
});

Per-route hooks compose with the scope-level hooks above them — both run, in order.

Avoid Heavy Work Where It Doesn’t Belong

Anything that needs the parsed body goes in preValidation or later. Anything that should run even on validation failures (rate limit, audit log) goes in onRequest. Authorization checks should be in preHandler so a 400 from invalid input doesn’t waste a JWT verify.

Application Hooks →