fastify

oriolrius's avatarfrom oriolrius

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.

⭐1stars🔀1forks📁View on GitHub🕐Updated Dec 4, 2025

When & Why to Use This Skill

This Claude skill provides expert-level guidance for the Fastify web framework, enabling developers to build high-performance, low-overhead Node.js web servers and REST APIs. It covers essential aspects including server configuration, TypeScript integration, schema-based validation, and plugin architecture to streamline backend development and ensure best practices.

Use Cases

  • Architecting high-performance RESTful APIs with automatic JSON Schema validation for robust data handling and security.
  • Developing type-safe Node.js applications by integrating Fastify with TypeScript for better maintainability and developer experience.
  • Implementing complex middleware logic and cross-cutting concerns using Fastify's powerful lifecycle hooks and modular plugin system.
  • Configuring secure and scalable web servers with built-in support for CORS, rate limiting, and security headers via the Fastify ecosystem.
  • Setting up real-time communication features and modern API layers using Fastify's WebSocket integration and tRPC support.
nameFastify
descriptionExpert 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