Introduction
Fastify est le framework Node.js le plus performant en 2026, surpassant Express de 2 à 3 fois en throughput grâce à son architecture zéro-dépendance et son système de plugins asynchrones. Idéal pour les APIs scalables sous haute charge, il excelle dans la validation de schémas JSON native (via Ajv), les hooks pour l'interception fine des requêtes, et l'écosystème de plugins officiels comme @fastify/jwt ou @fastify/rate-limit.
Ce tutoriel avancé vous guide pas à pas pour construire une API complète : authentification JWT, rate limiting, logging structuré, gestion d'erreurs custom, et clustering pour la production. Chaque étape inclut du code TypeScript fonctionnel, prêt à copier-coller. À la fin, vous maîtriserez les patterns experts pour des APIs qui gèrent des millions de requêtes/jour sans transpirer. Pourquoi Fastify ? Parce qu'il réduit le JSON parsing de 80% et supporte nativement TypeScript, rendant vos déploiements plus fiables et rapides. (148 mots)
Prérequis
- Node.js 20+ (avec npm ou yarn)
- Connaissances avancées en TypeScript et Node.js
- Un éditeur comme VS Code avec extension TypeScript
- Outils de test : Tap (inclus dans Fastify) ou Jest
- Base de données optionnelle (ex: PostgreSQL pour démo)
Installation et configuration initiale
mkdir fastify-advanced-api && cd fastify-advanced-api
npm init -y
npm install fastify@latest @fastify/rate-limit @fastify/jwt @fastify/swagger @fastify/cors @fastify/autoload
npm install -D @types/node typescript tsx
npm install @fastify/merge-json-schemas
echo '{
"type": "module",
"scripts": {
"dev": "tsx watch src/server.ts",
"start": "tsx src/server.ts"
}
}' > package.json
mkdir src
mkdir src/plugins
mkdir src/routes
mkdir src/schemasCette commande initialise un projet ESM moderne avec les plugins essentiels : rate-limit pour la protection DDoS, JWT pour l'auth, Swagger pour la doc auto, CORS pour les frontends. tsx permet l'exécution directe de TypeScript sans build. Les dossiers structurent le projet en suivant les best practices Fastify (autoload des routes/plugins).
Structure du projet Fastify
Fastify encourage une architecture modulaire : src/server.ts comme point d'entrée, plugins/ pour les services globaux (ex: logger, auth), routes/ pour les handlers, schemas/ pour la validation JSON. Utilisez register pour composer l'app comme des Lego, évitant le callback hell.
Serveur de base avec autoload et CORS
import Fastify from 'fastify';
import cors from '@fastify/cors';
import autoload from '@fastify/autoload';
import path from 'path';
const fastify = Fastify({
logger: {
level: 'info',
transport: {
target: 'pino-pretty',
options: { colorize: true }
}
}
});
await fastify.register(cors, { origin: '*' });
await fastify.register(autoload, {
dir: path.join(__dirname, 'plugins'),
options: Object.assign({ prefix: '/api' })
});
await fastify.register(autoload, {
dir: path.join(__dirname, 'routes'),
options: Object.assign({ prefix: '/api' })
});
fastify.get('/health', async (request, reply) => {
return { status: 'OK', timestamp: new Date().toISOString() };
});
const start = async () => {
try {
await fastify.listen({ port: 3000, host: '0.0.0.0' });
fastify.log.info(`Serveur démarré sur http://localhost:3000`);
} catch (err) {
fastify.log.error(err);
process.exit(1);
}
};
start();Ce serveur active le logger Pino (le plus rapide), CORS pour les appels cross-origin, et autoload pour charger auto tous les plugins/routes dans /plugins et /routes. Le hook listen gère les erreurs de démarrage. Testez avec npm run dev : curl http://localhost:3000/health retourne un JSON valide.
Plugin de rate limiting global
import rateLimit from '@fastify/rate-limit';
export default async function (fastify: any) {
await fastify.register(rateLimit, {
max: 100,
timeWindow: '1 minute',
keyGenerator: (request: any) => {
return request.ip || request.headers['x-forwarded-for'] || 'unknown';
},
skipSuccessfulRequests: false,
onExceeding: async (request: any, reply: any) => {
reply.code(429).send({
error: 'Trop de requêtes',
retryAfter: 60
});
}
});
fastify.addHook('preHandler', async (request: any, reply: any) => {
await fastify.rateLimit(request, reply);
});
}Ce plugin applique un rate limit de 100 req/min par IP, avec hook preHandler pour l'exécution avant chaque route. keyGenerator utilise l'IP réelle derrière les proxies. Sur dépassement, renvoie un 429 custom. Idéal contre les abus sans impacter les perfs.
Validation de schémas JSON avancée
Analogie : Les schémas Fastify sont comme des contrats JSON Schema : ils valident inputs/outputs automatiquement, rejettent les mauvais payloads avant le handler (économie CPU). Ajoutez $merge pour composer des schémas réutilisables.
Schémas réutilisables et routes validées
import { FastifyInstance } from 'fastify';
import { createUserSchema, getUserSchema } from '../schemas/user.js';
export async function usersRoutes(fastify: FastifyInstance) {
fastify.post('/users', { schema: createUserSchema }, async (request, reply) => {
const { email, password, name } = request.body as any;
// Simule DB insert
const userId = 'uuid-' + Date.now();
reply.code(201).send({ id: userId, email });
});
fastify.get('/users/:id', { schema: getUserSchema }, async (request) => {
const { id } = request.params as any;
return { id, email: 'user@example.com', name: 'John Doe' };
});
}Les schémas valident body/params/response automatiquement (400/500 si invalide). format: 'email' et minLength sont des validators Ajv puissants. La route /api/users est protégée : testez avec curl -X POST ... un mauvais email → erreur 400 structurée.
Plugin JWT avec hooks d'auth
import jwt from '@fastify/jwt';
export default async function (fastify: any) {
await fastify.register(jwt, {
secret: 'supersecret2026',
sign: { expiresIn: '1h' }
});
fastify.decorate('authenticate', async function (request: any, reply: any) {
try {
await request.jwtVerify();
} catch (err) {
reply.send(err);
}
});
fastify.addHook('preValidation', async (request: any, reply: any) => {
if (request.url.startsWith('/api/protected')) {
await request.jwtVerify();
}
});
}Ce plugin ajoute jwtVerify() global et un décorateur réutilisable authenticate. Hook preValidation protège auto les routes /protected. Générez un token via une route login (à ajouter), puis utilisez Authorization: Bearer . Évite les boilerplates dans chaque handler.
Hooks avancés et logging
Les hooks Fastify (onRequest, preHandler, preValidation, postHandler) interceptent le cycle de vie comme des middleware mais asynchrones et parallélisables. Couplez-les au logger Pino pour des traces structurées : { reqId, level, msg }.
Gestion d'erreurs custom et Swagger
import swagger from '@fastify/swagger';
import swaggerUI from '@fastify/swagger-ui';
export default async function (fastify: any) {
await fastify.register(swagger, {
openapi: {
info: {
title: 'Fastify Advanced API',
version: '1.0.0'
}
}
});
await fastify.register(swaggerUI, {
routePrefix: '/docs'
});
};
fastify.setErrorHandler((error: any, request: any, reply: any) => {
const statusCode = error.statusCode || 500;
fastify.log.error({ err: error, reqId: request.id }, 'Erreur');
reply.code(statusCode).send({
error: error.message,
timestamp: new Date().toISOString()
});
});Swagger génère une doc interactive à /docs. L'ErrorHandler global capture toutes les erreurs, logge avec request.id (auto-généré), et renvoie un JSON propre. Ajoutez-le dans server.ts après les registers. Testez : forcez une erreur → log structuré + réponse 500.
Clustering pour production
import Fastify from 'fastify';
import cluster from 'cluster';
import os from 'os';
const numCPUs = os.cpus().length;
if (cluster.isPrimary) {
console.log(`Master démarré, fork ${numCPUs} workers`);
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker) => {
console.log(`Worker ${worker.process.pid} mort`);
cluster.fork();
});
} else {
// Collez ici le code de server.ts (imports, fastify instance, listen)
const fastify = Fastify({ logger: true });
// ... tous les registers ...
await fastify.listen({ port: 3000, host: '0.0.0.0' });
}Utilise le module cluster natif Node pour scaler sur tous les CPU cores. Le primary fork les workers ; exit handler redémarre les crashés (zero-downtime). En prod, booste le throughput x4-8. Lancez avec tsx src/server.prod.ts.
Bonnes pratiques
- Plugins avant routes : Toujours
register(plugins)puisautoload(routes)pour l'ordre d'exec. - Schémas par route : Validez response pour des contrats API stricts (OpenAPI compliant).
- Hooks légers : Évitez les DB calls dans
onRequest; réservezpreHandlerpour l'auth. - Logger structuré : Utilisez
request.logpartout pour corréler req/err. - Tests unitaires : Injectez Fastify dans
fastify.inject()pour des tests sans serveur.
Erreurs courantes à éviter
- Oublier
awaitsurregister(): provoque des routes non-chargées silencieusement. - Ignorer les conflits de schémas : utilisez
@fastify/merge-json-schemaspour$merge. - Rate limit sans
keyGeneratorcustom : échoue derrière Cloudflare/NGINX. - Pas de clustering en prod : gaspille 80% des CPU multi-cores.
Pour aller plus loin
- Plugins avancés :
@fastify/postgrespour DB poolée,@fastify/redispour cache. - Benchmark :
autocannon -c 100 -d 20 http://localhost:3000/api/health. - Déploiement : Docker + PM2 ou
fastify-cluster.