Express: A Safe Choice or a Performance Bottleneck?
Building a REST API with Express is incredibly easy. It’s the first name that comes to mind for every Node.js developer, thanks to its massive community and extensive documentation. However, when traffic hits the threshold of several thousand requests per second, Express starts showing performance “cracks” that I never noticed before.
The fatal weakness of Express lies in its overhead. It uses a regex-based routing mechanism that is quite slow. Additionally, Express doesn’t support optimized JSON serialization. Every time the server returns data, Node.js must manually perform JSON.stringify() on the entire object. This is extremely CPU-intensive and can block the Event Loop when objects are too large.
After many sleepless nights optimizing middleware with lackluster results, I decided to experiment with Fastify. The results were shocking: throughput soared while latency dropped to a minimum, making it a strong contender for building blazing fast REST APIs.
Why is Fastify Unbelievably Fast?
Fastify is not just another web framework. It was built with a “performance-first” mindset from the very first line of code. Here are the two secret weapons that help it outperform Express.
1. Routing via Radix Tree
Instead of traversing a list of routes using regex like Express, Fastify uses a Radix Tree data structure (via the find-my-way library). Route lookups now only depend on the path length (O(L)), regardless of whether you have 10 or 1,000 endpoints. For complex microservices systems, this is a massive leap in speed.
2. Serialization: Speeding Up with Schemas
This is my favorite feature. Fastify uses the fast-json-stringify library. Instead of waiting until the response is sent to convert data, Fastify requires you to define a JSON Schema beforehand. From this schema, it generates a specialized function to “mold” the object into a string. This approach is 2-3 times faster than standard JSON.stringify().
Implementing a Real-World REST API with Fastify
Get started quickly with a few familiar commands:
mkdir fastify-api-demo
cd fastify-api-demo
npm init -y
npm i fastify
Let’s look at how to define an endpoint. You’ll see the Schema appearing right within the code structure:
const fastify = require('fastify')({ logger: true });
// Define Schema to optimize response
const userSchema = {
response: {
200: {
type: 'object',
properties: {
id: { type: 'integer' },
name: { type: 'string' },
email: { type: 'string' }
}
}
}
};
fastify.get('/user/:id', { schema: userSchema }, async (request, reply) => {
// Database retrieval logic goes here
return { id: request.params.id, name: 'ItFromZero', email: '[email protected]' };
});
const start = async () => {
try {
await fastify.listen({ port: 3000 });
console.log('Server is running on port 3000');
} catch (err) {
fastify.log.error(err);
process.exit(1);
}
};
start();
To quickly check the returned JSON structure or format your schema nicely, I often use toolcraft.app. It’s more convenient than installing more extensions into an already heavy VS Code.
Plugin System: Divide and Conquer
A classic mistake when using Express is stuffing everything into the app.js file. Fastify solves this problem completely with its Plugin System. In Fastify, everything is a plugin: from routes and database connections to error handling logic.
The fastify-plugin mechanism allows for perfect logic encapsulation. If you register a plugin within a specific scope, its variables or decorators won’t “pollute” other parts of the application. Your code remains extremely clean and maintainable.
Validation: Stopping Junk Data at the Gate
Validation is often a step that slows down the system. However, Fastify comes with built-in Ajv to validate input data. This not only improves security but also helps Node.js optimize memory since data structures are always clearly defined.
For example, to validate the body of a POST request:
const postSchema = {
body: {
type: 'object',
required: ['title', 'content'],
properties: {
title: { type: 'string', minLength: 5 },
content: { type: 'string' }
}
}
};
fastify.post('/posts', { schema: postSchema }, async (request, reply) => {
return { status: 'Post created successfully!' };
});
If a client omits the title, Fastify automatically returns a 400 error with a detailed message. You don’t need to write a single line of validation logic, which is essential for projects like integrating PayOS with Node.js where data integrity is critical.
Real-World Numbers: Fastify vs. Express
I ran a benchmark using the autocannon tool on a MacBook M1 (16GB RAM). The test scenario was a simple endpoint returning JSON.
- Express: Reached ~12,000 req/s, average latency 15ms.
- Fastify: Reached ~32,000 req/s, average latency only 4ms.
The results show that Fastify handles nearly 3 times the requests of Express. In a production environment, this translates to significant savings on server costs (CPU/RAM).
Conclusion: When Should You Make the Switch to Fastify?
While Fastify is powerful, it doesn’t mean you should switch at all costs.
Choose Express when:
- You need to build a prototype extremely quickly for a small project.
- Your team is already very familiar with it and doesn’t have time to learn something new.
- The project depends on specific middleware only available for Express.
Choose Fastify when:
- Building microservices that require peak performance.
- The system needs to handle high loads with limited hardware resources.
- You want strictly structured code with enforced validation to minimize bugs.
Switching isn’t too difficult because the middleware philosophy between the two is quite similar. However, the benefits in speed and stability that Fastify provides are well worth the experience.

