onError Hook

Centralized Error Logging & Reporting

onError Hook

onError fires on every uncaught error from a handler — log to Sentry or Datadog here without changing the response shape.

4 min read Level 2/5 #fastify#hooks#errors
What you'll learn
  • Add onError to report errors to Sentry
  • Keep the response shape in setErrorHandler
  • Avoid throwing inside onError

onError is the observation point for failed requests. It fires after a handler throws (or rejects) and before the response is sent, but it does not change the response — that is the job of setErrorHandler.

Report to Sentry

import * as Sentry from '@sentry/node';

app.addHook('onError', async (req, reply, err) => {
  Sentry.captureException(err, {
    extra: {
      reqId: req.id,
      url: req.url,
      method: req.method,
    },
    user: req.user ? { id: req.user.id } : undefined,
  });
});

req.id ties the Sentry event back to the log line. Add user context if your auth hook populated req.user.

Skip Validation Errors

Validation errors (err.validation) are usually client mistakes, not bugs. Filter them out so your dashboards stay clean:

app.addHook('onError', async (req, reply, err) => {
  if (err.validation) return; // 400s aren't bugs
  if (err.statusCode && err.statusCode < 500) return;
  Sentry.captureException(err);
});

Don’t Throw From onError

If onError throws, Fastify falls back to the default error handler and your custom error shape is lost. Wrap risky work in try/catch:

app.addHook('onError', async (req, reply, err) => {
  try {
    await reportToDatadog(err, req);
  } catch (reportErr) {
    req.log.warn({ reportErr }, 'failed to report error');
  }
});

Roles, Clearly Divided

onError — observe. setErrorHandler — shape the response. onResponse — collect metrics after the bytes are sent. Each has a single job; that separation keeps the lifecycle easy to reason about as the app grows.

Connecting to a Database →