Fastify Core Knowledge
Full Reference: See advanced.md for WebSocket authentication, room management, heartbeat patterns, message validation, and Redis scaling.
Deep Knowledge: Use mcp__documentation__fetch_docs with technology: fastify for comprehensive documentation.
Basic Setup
import Fastify from 'fastify'; import cors from '@fastify/cors';
const app = Fastify({ logger: true });
await app.register(cors); await app.register(userRoutes, { prefix: '/api/users' });
app.listen({ port: 3000, host: '0.0.0.0' });
Route with Schema Validation
import { FastifyPluginAsync } from 'fastify'; import { Type, Static } from '@sinclair/typebox';
const UserSchema = Type.Object({ name: Type.String(), email: Type.String({ format: 'email' }), });
type User = Static<typeof UserSchema>;
const routes: FastifyPluginAsync = async (app) => { app.post<{ Body: User }>('/', { schema: { body: UserSchema, response: { 201: UserSchema } } }, async (request, reply) => { const user = await db.users.create(request.body); reply.status(201).send(user); }); };
Plugins
import fp from 'fastify-plugin';
// Custom plugin with fastify-plugin export default fp(async (app) => { app.decorate('db', db); app.addHook('onRequest', async (request) => { request.startTime = Date.now(); }); });
app.register(myPlugin);
Error Handling
import { FastifyError, FastifyReply, FastifyRequest } from 'fastify';
export class AppError extends Error { constructor(public statusCode: number, message: string) { super(message); } }
app.setErrorHandler((error: FastifyError, request, reply) => { request.log.error({ err: error }, 'Request error');
if (error.validation) { return reply.status(400).send({ error: 'Validation failed', details: error.validation }); }
return reply.status(error.statusCode || 500).send({ error: error.message }); });
Health Checks
app.get('/health', async () => ({ status: 'healthy' }));
app.get('/ready', async (request, reply) => { try { await app.pg.query('SELECT 1'); return { status: 'ready', database: 'connected' }; } catch (error) { reply.status(503); return { status: 'not ready' }; } });
Testing
import { test } from 'vitest'; import { buildApp } from '../src/app';
test('GET /api/users returns users', async () => { const app = await buildApp();
const response = await app.inject({ method: 'GET', url: '/api/users', });
expect(response.statusCode).toBe(200); await app.close(); });
WebSocket Setup
import websocket from '@fastify/websocket';
await app.register(websocket);
app.get('/ws', { websocket: true }, (socket, req) => { socket.on('message', (data) => { const message = JSON.parse(data.toString()); handleMessage(socket, message); }); });
When NOT to Use This Skill
-
Enterprise Architecture - Use NestJS for DI and modular design
-
Simple CRUD APIs - Use Express if performance is not critical
-
Edge Runtimes - Use Hono for Cloudflare Workers
-
Deno Projects - Use Oak or Fresh instead
Anti-Patterns
Anti-Pattern Why It's Bad Correct Approach
Not using schema validation Loses performance advantage Define schemas for routes
Missing fastify-plugin wrapper Encapsulation issues Use fp() for shared plugins
Using app.listen() in tests Slow, port conflicts Use app.inject()
Ignoring response schema Slower serialization Define response schemas
Quick Troubleshooting
Issue Likely Cause Solution
"Cannot call reply.send twice" Multiple sends Return after first reply.send()
Plugin not accessible Missing fastify-plugin Wrap with fp()
Schema not working Not registered Add schema: {} to route options
Type errors with schemas Provider missing Use withTypeProvider<TypeBoxTypeProvider>()
Production Checklist
-
TypeBox schema validation
-
Helmet security headers
-
Rate limiting configured
-
Structured logging (Pino)
-
Request ID tracing
-
Custom error handler
-
Health/readiness endpoints
-
Graceful shutdown
-
Database connection pooling
Monitoring Metrics
Metric Target
Response time (p99) < 50ms
Error rate (5xx) < 0.1%
Request throughput
10k/s
Reference Documentation
-
Schema Validation
-
Plugins