Fastify

Expert guidance for Fastify web framework including server setup, routing, plugins, hooks, validation, error handling, and TypeScript integration. Use this when building high-performance Node.js web servers and REST APIs.

$ Instalar

git clone https://github.com/oriolrius/pki-manager-web /tmp/pki-manager-web && cp -r /tmp/pki-manager-web/.claude/skills/fastify ~/.claude/skills/pki-manager-web

// tip: Run this command in your terminal to install the skill


name: Fastify description: Expert guidance for Fastify web framework including server setup, routing, plugins, hooks, validation, error handling, and TypeScript integration. Use this when building high-performance Node.js web servers and REST APIs.

Fastify

Expert assistance with Fastify - Fast and low overhead web framework for Node.js.

Overview

Fastify is a highly performant web framework:

  • Fast: One of the fastest Node.js frameworks
  • Low Overhead: Minimal resource consumption
  • Schema-based: JSON Schema validation
  • TypeScript: Excellent TypeScript support
  • Plugin Architecture: Extensible with plugins
  • Logging: Built-in logging with Pino

Installation

npm install fastify
npm install --save-dev @types/node

# Common plugins
npm install @fastify/cors          # CORS support
npm install @fastify/websocket     # WebSocket support
npm install @fastify/cookie        # Cookie parsing
npm install @fastify/jwt           # JWT authentication
npm install @fastify/helmet        # Security headers
npm install @fastify/rate-limit    # Rate limiting

Quick Start

import Fastify from 'fastify';

const server = Fastify({
  logger: true, // Enable Pino logging
});

server.get('/ping', async (request, reply) => {
  return { pong: 'it worked!' };
});

await server.listen({ port: 3000, host: '0.0.0.0' });
console.log('Server listening on http://localhost:3000');

Server Configuration

import Fastify from 'fastify';

const server = Fastify({
  logger: {
    level: 'info',
    transport: {
      target: 'pino-pretty', // Pretty printing in development
    },
  },
  bodyLimit: 1048576, // 1MB body limit
  caseSensitive: true, // Case-sensitive routes
  ignoreTrailingSlash: false,
  requestIdHeader: 'x-request-id',
  requestIdLogLabel: 'reqId',
  trustProxy: true, // Trust proxy headers
});

Routing

Basic Routes

// GET
server.get('/users', async (request, reply) => {
  return [{ id: 1, name: 'John' }];
});

// POST
server.post('/users', async (request, reply) => {
  const { name, email } = request.body;
  return { id: 2, name, email };
});

// PUT
server.put('/users/:id', async (request, reply) => {
  const { id } = request.params;
  const { name } = request.body;
  return { id, name };
});

// DELETE
server.delete('/users/:id', async (request, reply) => {
  const { id } = request.params;
  return { deleted: id };
});

// PATCH
server.patch('/users/:id', async (request, reply) => {
  return { updated: true };
});

Route Parameters

// URL parameters
server.get<{
  Params: { id: string };
}>('/users/:id', async (request, reply) => {
  const { id } = request.params; // Typed!
  return { id };
});

// Multiple parameters
server.get<{
  Params: { userId: string; postId: string };
}>('/users/:userId/posts/:postId', async (request, reply) => {
  const { userId, postId } = request.params;
  return { userId, postId };
});

// Query parameters
server.get<{
  Querystring: { search?: string; limit?: number };
}>('/search', async (request, reply) => {
  const { search, limit = 10 } = request.query;
  return { search, limit };
});

TypeScript Types

import { FastifyRequest, FastifyReply } from 'fastify';

interface CreateUserBody {
  name: string;
  email: string;
}

interface UserParams {
  id: string;
}

server.post<{
  Body: CreateUserBody;
}>('/users', async (request, reply) => {
  const { name, email } = request.body; // Fully typed
  return { id: '1', name, email };
});

server.get<{
  Params: UserParams;
}>('/users/:id', async (request, reply) => {
  const { id } = request.params;
  return { id };
});

Validation

JSON Schema Validation

const createUserSchema = {
  body: {
    type: 'object',
    required: ['name', 'email'],
    properties: {
      name: { type: 'string', minLength: 2 },
      email: { type: 'string', format: 'email' },
      age: { type: 'number', minimum: 18 },
    },
  },
  response: {
    201: {
      type: 'object',
      properties: {
        id: { type: 'string' },
        name: { type: 'string' },
        email: { type: 'string' },
      },
    },
  },
};

server.post('/users', {
  schema: createUserSchema,
}, async (request, reply) => {
  const { name, email, age } = request.body;
  reply.status(201);
  return { id: '1', name, email };
});

Plugins

Register Plugins

import cors from '@fastify/cors';
import helmet from '@fastify/helmet';
import rateLimit from '@fastify/rate-limit';

// CORS
await server.register(cors, {
  origin: true, // Reflect origin
  credentials: true,
});

// Security headers
await server.register(helmet);

// Rate limiting
await server.register(rateLimit, {
  max: 100, // 100 requests
  timeWindow: '1 minute',
});

Custom Plugin

import fp from 'fastify-plugin';

const myPlugin = fp(async (fastify, options) => {
  // Add decorator
  fastify.decorate('myUtility', () => {
    return 'Hello from plugin!';
  });

  // Add hook
  fastify.addHook('onRequest', async (request, reply) => {
    // Do something on every request
  });
}, {
  name: 'my-plugin',
  fastify: '4.x',
});

await server.register(myPlugin);

// Use decorator
server.get('/test', async (request, reply) => {
  return { message: server.myUtility() };
});

Hooks

// Application hooks
server.addHook('onRequest', async (request, reply) => {
  // Called before route handler
  request.log.info('Incoming request');
});

server.addHook('preHandler', async (request, reply) => {
  // Called after validation, before handler
  if (!request.headers.authorization) {
    reply.code(401).send({ error: 'Unauthorized' });
  }
});

server.addHook('onSend', async (request, reply, payload) => {
  // Called before sending response
  return payload;
});

server.addHook('onResponse', async (request, reply) => {
  // Called after response sent
  request.log.info({ responseTime: reply.getResponseTime() });
});

server.addHook('onError', async (request, reply, error) => {
  // Called on error
  request.log.error(error);
});

Error Handling

// Custom error handler
server.setErrorHandler((error, request, reply) => {
  request.log.error(error);

  if (error.validation) {
    reply.status(400).send({
      error: 'Validation Error',
      message: error.message,
      details: error.validation,
    });
    return;
  }

  reply.status(error.statusCode || 500).send({
    error: error.name,
    message: error.message,
  });
});

// Throw errors in routes
server.get('/error', async (request, reply) => {
  throw new Error('Something went wrong!');
});

// Send error responses
server.get('/not-found', async (request, reply) => {
  reply.code(404).send({ error: 'Not found' });
});

tRPC Integration

import { fastifyTRPCPlugin } from '@trpc/server/adapters/fastify';
import { appRouter } from './trpc/router';
import { createContext } from './trpc/context';

// Register tRPC
await server.register(fastifyTRPCPlugin, {
  prefix: '/trpc',
  trpcOptions: {
    router: appRouter,
    createContext,
  },
});

WebSocket Support

import websocket from '@fastify/websocket';

await server.register(websocket);

server.get('/ws', { websocket: true }, (connection, request) => {
  connection.socket.on('message', (message) => {
    connection.socket.send('Hello from server!');
  });
});

Testing

import { test } from 'node:test';
import Fastify from 'fastify';

test('GET /ping returns pong', async (t) => {
  const server = Fastify();

  server.get('/ping', async () => {
    return { pong: 'it worked!' };
  });

  const response = await server.inject({
    method: 'GET',
    url: '/ping',
  });

  t.assert.strictEqual(response.statusCode, 200);
  t.assert.deepStrictEqual(response.json(), { pong: 'it worked!' });
});

Best Practices

  1. Use Plugins: Encapsulate functionality in plugins
  2. Schema Validation: Always validate input with JSON Schema
  3. Error Handling: Set up global error handler
  4. Logging: Use built-in Pino logger
  5. TypeScript: Leverage type safety
  6. Hooks: Use hooks for cross-cutting concerns
  7. Async/Await: Use async handlers
  8. Testing: Use fastify.inject() for testing
  9. Performance: Enable HTTP/2 for better performance
  10. Security: Use @fastify/helmet for security headers

Resources