تخطَّ إلى المحتوى

Fastify Cheat Sheet

Overview

Fastify is a web framework for Node.js focused on providing the best developer experience with the least overhead and a powerful plugin architecture. It is one of the fastest web frameworks available, capable of serving up to 76,000 requests per second. Fastify uses JSON Schema for request/response validation and serialization, which not only provides data validation but also accelerates JSON serialization by up to 2-3x compared to JSON.stringify.

Inspired by Hapi and Express, Fastify was created by Matteo Collina and Tomas Della Vedova. It provides first-class TypeScript support, a composable plugin system based on avvio, built-in logging via Pino, and automatic OpenAPI/Swagger documentation generation. The framework is designed for building efficient APIs and microservices while maintaining developer productivity.

Installation

Setup

# Create project
mkdir my-api && cd my-api
npm init -y

# Install Fastify
npm install fastify

# With CLI scaffolding
npm install -g fastify-cli
fastify generate my-api --lang=ts
cd my-api && npm install

# TypeScript setup
npm install typescript @types/node tsx --save-dev

Minimal Server

import Fastify from 'fastify';

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

fastify.get('/', async (request, reply) => {
  return { hello: 'world' };
});

fastify.listen({ port: 3000, host: '0.0.0.0' }, (err) => {
  if (err) {
    fastify.log.error(err);
    process.exit(1);
  }
});

Routing

Route Methods

// Short-hand
fastify.get('/users', handler);
fastify.post('/users', handler);
fastify.put('/users/:id', handler);
fastify.patch('/users/:id', handler);
fastify.delete('/users/:id', handler);
fastify.head('/users', handler);
fastify.options('/users', handler);

// Full declaration
fastify.route({
  method: 'GET',
  url: '/users/:id',
  schema: {
    params: {
      type: 'object',
      properties: {
        id: { type: 'string' }
      }
    },
    response: {
      200: {
        type: 'object',
        properties: {
          id: { type: 'string' },
          name: { type: 'string' },
          email: { type: 'string' }
        }
      }
    }
  },
  handler: async (request, reply) => {
    const { id } = request.params as { id: string };
    return getUserById(id);
  }
});

// Parametric routes
fastify.get('/users/:id', handler);           // /users/123
fastify.get('/files/*', handler);              // /files/path/to/file
fastify.get('/example/:id(^\\d+)', handler);  // Regex constraint

Route Options

fastify.get('/protected', {
  preHandler: [authenticate],
  schema: {
    headers: {
      type: 'object',
      required: ['authorization'],
      properties: {
        authorization: { type: 'string' }
      }
    }
  },
  handler: async (request, reply) => {
    return { user: request.user };
  }
});

Schema Validation

JSON Schema Validation

const createUserSchema = {
  body: {
    type: 'object',
    required: ['name', 'email'],
    properties: {
      name: { type: 'string', minLength: 1, maxLength: 100 },
      email: { type: 'string', format: 'email' },
      age: { type: 'integer', minimum: 0, maximum: 150 }
    },
    additionalProperties: false
  },
  response: {
    201: {
      type: 'object',
      properties: {
        id: { type: 'string' },
        name: { type: 'string' },
        email: { type: 'string' },
        createdAt: { type: 'string', format: 'date-time' }
      }
    },
    400: {
      type: 'object',
      properties: {
        error: { type: 'string' },
        message: { type: 'string' }
      }
    }
  }
};

fastify.post('/users', { schema: createUserSchema }, async (request, reply) => {
  const { name, email, age } = request.body as CreateUserBody;
  const user = await createUser({ name, email, age });
  reply.code(201);
  return user;
});

Query String and Params

fastify.get('/search', {
  schema: {
    querystring: {
      type: 'object',
      properties: {
        q: { type: 'string' },
        page: { type: 'integer', default: 1, minimum: 1 },
        limit: { type: 'integer', default: 20, minimum: 1, maximum: 100 }
      },
      required: ['q']
    }
  }
}, async (request) => {
  const { q, page, limit } = request.query as SearchQuery;
  return search(q, page, limit);
});

Plugins

Creating Plugins

import fp from 'fastify-plugin';

// Plugin with options
const dbPlugin = fp(async (fastify, opts) => {
  const pool = await createPool(opts.connectionString);

  fastify.decorate('db', pool);

  fastify.addHook('onClose', async () => {
    await pool.end();
  });
}, {
  name: 'database',
  fastify: '4.x'
});

// Register plugin
fastify.register(dbPlugin, {
  connectionString: process.env.DATABASE_URL
});

// Scoped plugin (encapsulated)
fastify.register(async (instance) => {
  instance.decorate('scopedValue', 42);
  // scopedValue only available in this scope
});

Common Plugins

PluginPurposeInstall
@fastify/corsCORS headersnpm i @fastify/cors
@fastify/jwtJWT authenticationnpm i @fastify/jwt
@fastify/swaggerOpenAPI docsnpm i @fastify/swagger
@fastify/rate-limitRate limitingnpm i @fastify/rate-limit
@fastify/cookieCookie handlingnpm i @fastify/cookie
@fastify/multipartFile uploadsnpm i @fastify/multipart
@fastify/staticStatic file servingnpm i @fastify/static
@fastify/websocketWebSocket supportnpm i @fastify/websocket
@fastify/helmetSecurity headersnpm i @fastify/helmet
@fastify/postgresPostgreSQLnpm i @fastify/postgres
import cors from '@fastify/cors';
import jwt from '@fastify/jwt';

await fastify.register(cors, { origin: true });
await fastify.register(jwt, { secret: process.env.JWT_SECRET });

Hooks (Lifecycle)

HookDescription
onRequestFirst hook, before parsing
preParsingBefore body parsing
preValidationBefore schema validation
preHandlerAfter validation, before handler
preSerializationBefore response serialization
onSendBefore sending response
onResponseAfter response sent
onErrorOn error
// Authentication hook
fastify.addHook('preHandler', async (request, reply) => {
  try {
    await request.jwtVerify();
  } catch (err) {
    reply.code(401).send({ error: 'Unauthorized' });
  }
});

// Logging hook
fastify.addHook('onResponse', async (request, reply) => {
  request.log.info({
    url: request.url,
    statusCode: reply.statusCode,
    responseTime: reply.elapsedTime
  });
});

Configuration

Fastify Options

const fastify = Fastify({
  logger: {
    level: 'info',
    transport: {
      target: 'pino-pretty',
      options: { colorize: true }
    }
  },
  trustProxy: true,
  maxParamLength: 200,
  bodyLimit: 1048576,  // 1MB
  caseSensitive: true,
  requestTimeout: 30000,
  keepAliveTimeout: 72000
});

Environment Configuration

import { envSchema } from 'env-schema';

const config = envSchema({
  schema: {
    type: 'object',
    required: ['PORT', 'DATABASE_URL'],
    properties: {
      PORT: { type: 'integer', default: 3000 },
      DATABASE_URL: { type: 'string' },
      NODE_ENV: { type: 'string', default: 'development' },
      LOG_LEVEL: { type: 'string', default: 'info' }
    }
  },
  dotenv: true
});

Advanced Usage

Error Handling

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

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

  reply.code(error.statusCode || 500).send({
    error: error.name || 'Internal Server Error',
    message: error.message
  });
});

// Not found handler
fastify.setNotFoundHandler((request, reply) => {
  reply.code(404).send({
    error: 'Not Found',
    message: `Route ${request.method} ${request.url} not found`
  });
});

Swagger/OpenAPI Documentation

import swagger from '@fastify/swagger';
import swaggerUi from '@fastify/swagger-ui';

await fastify.register(swagger, {
  openapi: {
    info: {
      title: 'My API',
      version: '1.0.0'
    },
    servers: [{ url: 'http://localhost:3000' }]
  }
});

await fastify.register(swaggerUi, {
  routePrefix: '/docs'
});

Testing

import { test } from 'node:test';
import assert from 'node:assert';
import buildApp from '../src/app.js';

test('GET / returns hello', async () => {
  const app = buildApp();

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

  assert.strictEqual(response.statusCode, 200);
  assert.deepStrictEqual(JSON.parse(response.body), { hello: 'world' });
});

Troubleshooting

ProblemSolution
FST_ERR_DEC_ALREADY_PRESENTDecorator name conflict; use unique names or fastify-plugin
Schema validation errorsCheck JSON Schema syntax; use ajv-errors for custom messages
Plugin not availableEnsure fastify-plugin wraps it to break encapsulation
Slow serializationDefine response schemas; Fastify uses fast-json-stringify
Memory leaksCheck hook cleanup; use onClose for resource cleanup
TypeScript type errorsUse declare module to extend Fastify interfaces
CORS issuesRegister @fastify/cors before routes
413 Payload Too LargeIncrease bodyLimit in Fastify options