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
-
Use Plugins: Encapsulate functionality in plugins
-
Schema Validation: Always validate input with JSON Schema
-
Error Handling: Set up global error handler
-
Logging: Use built-in Pino logger
-
TypeScript: Leverage type safety
-
Hooks: Use hooks for cross-cutting concerns
-
Async/Await: Use async handlers
-
Testing: Use fastify.inject() for testing
-
Performance: Enable HTTP/2 for better performance
-
Security: Use @fastify/helmet for security headers
Resources
-
Documentation: https://www.fastify.io/docs/latest/
-
Plugins: https://www.fastify.io/ecosystem/