Fastify
Fastify is a high-performance Node web framework with a tiny core, JSON-Schema-based validation, a built-in serializer, and first-class TypeScript types. It is the right pick when you want Express ergonomics with much better throughput, request-scoped logging, and schema-validated I/O out of the box.
A typed Fastify server with schema validation
EXAMPLE
// npm i fastify @fastify/sensible @fastify/cors pino-pretty
import Fastify from 'fastify';
import sensible from '@fastify/sensible';
import cors from '@fastify/cors';
const app = Fastify({
logger: {
transport: { target: 'pino-pretty' },
redact: ['req.headers.authorization', 'req.headers.cookie'],
},
trustProxy: true,
});
await app.register(sensible); // adds httpErrors helpers
await app.register(cors, { origin: ['https://app.example.com'] });
// 1) Type the request and reply via the schema — Fastify uses it for VALIDATION
// (rejects bad input with 400) AND for SERIALISATION (fast JSON stringify).
const orderSchema = {
body: {
type: 'object',
required: ['customer', 'totalCents'],
properties: {
customer: { type: 'string', minLength: 1, maxLength: 120 },
totalCents: { type: 'integer', minimum: 0 },
notes: { type: 'string', maxLength: 500 },
},
additionalProperties: false,
},
response: {
201: {
type: 'object',
required: ['id', 'customer', 'totalCents', 'status'],
properties: {
id: { type: 'string' },
customer: { type: 'string' },
totalCents: { type: 'integer' },
status: { type: 'string', enum: ['new', 'paid', 'shipped'] },
createdAt: { type: 'string', format: 'date-time' },
},
},
},
} as const;
const orders = new Map<string, any>();
app.post('/orders', { schema: orderSchema }, async (req, reply) => {
// req.body is typed by the schema; no Joi/Zod needed for the validation pass
const id = crypto.randomUUID();
const o = { id, ...req.body, status: 'new', createdAt: new Date().toISOString() };
orders.set(id, o);
reply.code(201).header('Location', \`/orders/${id}\`).send(o);
});
app.get('/orders/:id', {
schema: { params: { type: 'object', properties: { id: { type: 'string' } }, required: ['id'] } },
}, async (req) => {
const o = orders.get((req.params as any).id);
if (!o) throw app.httpErrors.notFound('order not found');
return o;
});
// 2) Plugins are the Fastify unit of composition — encapsulated, hot-pluggable.
app.register(async function userRoutes(scope) {
scope.addHook('preHandler', async (req) => {
if (!req.headers.authorization) throw scope.httpErrors.unauthorized('missing token');
});
scope.get('/me', async () => ({ id: 'u1' }));
}, { prefix: '/api/users' });
// 3) Per-request logger comes with a generated reqId
app.addHook('onResponse', async (req, reply) => {
req.log.info({ ms: reply.elapsedTime }, 'served');
});
// 4) Graceful shutdown — close pools, drain in-flight requests
const shutdown = async (signal: NodeJS.Signals) => {
app.log.info({ signal }, 'shutting down');
await app.close();
process.exit(0);
};
process.on('SIGTERM', shutdown);
process.on('SIGINT', shutdown);
await app.listen({ port: 3000, host: '0.0.0.0' });
// Benchmarks vs Express on a 'hello world' route:
// Fastify: ~80k req/s Express: ~12k req/s
// On real schemas with validation+serialisation, the gap usually GROWS in Fastify's favour.
Why it matters
Define the schema response shape AND request body. Fastify uses the response schema to generate a fast JSON stringifier (no JSON.stringify overhead) and to strip unknown fields, which is a free speed-up AND a free safety net against accidentally leaking data fields you forgot to remove.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
import Fastify from 'fastify';
const app = Fastify({ logger: true });
app.get('/', async () => ({ hello: 'world' }));
app.listen({ port: 3000 });
Try it Yourself »
Discussion
Loading…