Topics Plugins & Decorators Decorators & Hooks
advanced 16 min read

Decorators & Hooks

Extend Fastify with custom decorators on the instance, request, and reply.

Decorators

// Instance decorator (available via fastify.decoratorName)\napp.decorate('config', {\n  appName: 'My API',\n  version: '1.0.0',\n});\n\n// Request decorator (available via request.decoratorName)\napp.decorateRequest('session', null);\n\napp.addHook('preHandler', async (request) => {\n  request.session = { userId: 1, role: 'admin' };\n});\n\n// Reply decorator (available via reply.decoratorName)\napp.decorateReply('success', function(data, statusCode = 200) {\n  this.code(statusCode).send({\n    success: true,\n    data,\n  });\n});\n\napp.decorateReply('fail', function(error, statusCode = 400) {\n  this.code(statusCode).send({\n    success: false,\n    error,\n  });\n});\n\n// Usage\napp.get('/profile', async (request, reply) => {\n  if (!request.session) {\n    return reply.fail('Not authenticated', 401);\n  }\n  const profile = await db.findUser(request.session.userId);\n  return reply.success(profile);\n});

Hook Types

// Lifecycle hooks (in order)\napp.addHook('onRequest', handler);       // Before parsing\napp.addHook('preParsing', handler);     // Before body parsing\napp.addHook('preValidation', handler);  // Before schema validation\napp.addHook('preHandler', handler);     // Before route handler\napp.addHook('preSerialization', handler); // Before response serialization\napp.addHook('onSend', handler);         // Before sending response\napp.addHook('onResponse', handler);     // After response sent\napp.addHook('onTimeout', handler);      // On request timeout\napp.addHook('onReady', handler);        // After all plugins loaded\napp.addHook('onClose', handler);        // On server close\napp.addHook('onRoute', handler);        // When a route is registered

Examples

const Fastify = require('fastify');
const app = Fastify({ logger: true });

// Request timing decorator
app.decorateRequest('timing', null);

app.addHook('onRequest', async (request) => {
  request.timing = { start: Date.now() };
});

app.addHook('onResponse', async (request, reply) => {
  if (request.timing) {
    request.timing.duration = Date.now() - request.timing.start;
    app.log.info({
      method: request.method,
      url: request.url,
      duration: `\${"$"}{request.timing.duration}ms`,
    });
  }
});

// Custom pagination decorator
app.decorateReply('paginated', function(items, total, opts = {}) {
  const page = opts.page || 1;
  const limit = opts.limit || 10;
  const totalPages = Math.ceil(total / limit);

  this.send({
    data: items,
    meta: {
      page,
      limit,
      total,
      totalPages,
      hasNext: page < totalPages,
      hasPrev: page > 1,
    },
  });
});

// Usage
app.get('/items', async (request, reply) => {
  const page = Number(request.query.page) || 1;
  const limit = Number(request.query.limit) || 10;
  const items = Array.from({ length: limit }, (_, i) => ({
    id: (page - 1) * limit + i + 1,
    name: `Item \${"$"}{(page - 1) * limit + i + 1}`,
  }));

  return reply.paginated(items, 100, { page, limit });
});

app.listen({ port: 3000 });

Your Notes

Sign in to take notes for this lesson.

Discussion

Sign in to join the discussion.

Flashcards

Sign in to create flashcards.