| 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
- 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/
- GitHub: https://github.com/fastify/fastify
- Plugins: https://www.fastify.io/ecosystem/