Why I Still Reach for Fastify Over Express
I used Express for years. It was the obvious choice — massive ecosystem, familiar API, near-zero learning curve. For a long time I didn't question it. Then I started building APIs where I actually cared about performance, schema consistency, and long-term maintainability, and Express started showing its age.
Fastify isn't new. But it's matured into something I now consider the better default for serious backend work in Node.js. Here's how I think about the tradeoffs.
Schema validation as a first-class citizen
The biggest difference in day-to-day development is how each framework handles input validation. With Express, validation is an afterthought — you bolt on joi, zod, or express-validator as middleware and hope it integrates cleanly with your error handlers.
Fastify builds validation into the route definition itself via JSON Schema. You declare the shape of your request body, query parameters, and response — and Fastify validates, coerces, and serializes accordingly. The response serialization alone gives you a meaningful performance advantage, because Fastify can skip the generic JSON.stringify path and use a compiled fast-path based on your schema.
// Fastify route with schema — validation is declared, not bolted on fastify.post('/api/bookings', { schema: { body: { type: 'object', required: ['professionalId', 'date'], properties: { professionalId: { type: 'string' }, date: { type: 'string', format: 'date-time' }, notes: { type: 'string', maxLength: 500 } } } } }, async (request, reply) => { const booking = await createBooking(request.body); return { id: booking.id, status: 'confirmed' }; });
With Zod 4 on top (via fastify-type-provider-zod), you get full TypeScript inference from your schema definitions at zero extra cost. The request body is typed, the reply is typed, and your IDE knows about it.
Performance that actually matters
Fastify's benchmarks are well-known — it consistently outperforms Express by a significant margin on raw throughput. In practice, for most applications, that difference is not the bottleneck. Your database queries, external API calls, and business logic will dominate latency long before the framework overhead matters.
But performance isn't just about req/sec numbers. Fastify's plugin system, lifecycle hooks, and encapsulation model make it easier to build applications that stay fast as they grow, because the architecture encourages you to keep concerns cleanly separated rather than piling middleware onto a global chain.
The plugin system is genuinely good
Express middleware is global by default. Fastify plugins are scoped and encapsulated using a tree model built on avvio. A plugin registered in one scope doesn't bleed into another unless you explicitly export it. For large applications with different authentication requirements across route groups, this is a substantial win.
The day I stopped fighting Express middleware order and started thinking in Fastify scopes, API design became significantly less painful.
When I still use Express
Honestly, not often anymore. The main case is when I'm integrating with an existing Express-based codebase or when a client's team is already deeply familiar with it and training cost outweighs technical benefit. For greenfield projects, Fastify is my default.
The ecosystem gap has closed considerably. Most important middleware has Fastify equivalents, and for anything that doesn't, writing a compatibility wrapper is usually straightforward. The documentation has improved dramatically with v4 and v5.
The bottom line
Fastify isn't the right tool for every situation. But if you're building a production API in Node.js and you haven't evaluated it recently, it's worth the few hours it takes to understand its model. The schema-first approach alone will save you debugging time that far outweighs the learning curve cost.