Fastify vs Express — Which is Faster?
Advertisement
Fastify is the new hotness in Node.js frameworks, promising 3x better throughput than Express. But is it worth switching? Let's compare both side by side.
Performance Comparison
Fastify is genuinely faster:
- Requests/sec: Fastify ~30,000 vs Express ~10,000
- Latency: Fastify ~1ms avg vs Express ~3ms avg
- Memory: Similar consumption
// Express hello world
import express from "express";
const app = express();
app.get("/", (req, res) => {
res.json({ hello: "world" });
});
app.listen(3000);
// Fastify hello world
import Fastify from "fastify";
const fastify = Fastify();
fastify.get("/", async (request, reply) => {
return { hello: "world" };
});
fastify.listen({ port: 3000 });
Feature Comparison
| Feature | Express | Fastify |
|---|---|---|
| Built-in routing | Basic | Radix tree (faster) |
| Schema validation | Via middleware | Built-in JSON Schema |
| Type support | Partial | Excellent |
| Plugin system | Middleware | Encapsulation-aware |
| Learning curve | Easy | Moderate |
| Ecosystem | Huge | Growing |
| Performance | Good | Excellent |
- Performance Comparison
- Feature Comparison
- Fastify Setup
- Plugins System
- Error Handling in Fastify
- Hooks System
- When to Use Fastify
- When to Use Express
- Real-World Benchmark
- Migration Path
- FAQ
Fastify Setup
import Fastify from "fastify";
import cors from "@fastify/cors";
import jwt from "@fastify/jwt";
const fastify = Fastify({ logger: true });
// Plugins
await fastify.register(cors);
await fastify.register(jwt, { secret: "secret" });
// Routes with schema validation
fastify.post<{ Body: { name: string; email: string } }>(
"/users",
{
schema: {
body: {
type: "object",
required: ["name", "email"],
properties: {
name: { type: "string" },
email: { type: "string", format: "email" },
},
},
response: {
201: {
type: "object",
properties: {
id: { type: "number" },
name: { type: "string" },
email: { type: "string" },
},
},
},
},
},
async (request, reply) => {
const { name, email } = request.body;
reply.code(201);
return { id: 1, name, email };
}
);
await fastify.listen({ port: 3000 });
Plugins System
// Custom Fastify plugin
import { FastifyPlugin } from "fastify";
const myPlugin: FastifyPlugin = async (fastify) => {
fastify.decorate("utils", {
hello: () => "Hello from plugin",
});
fastify.get("/hello", async () => {
return { message: fastify.utils.hello() };
});
};
fastify.register(myPlugin);
// Express middleware (for comparison)
app.use((req, res, next) => {
req.app.locals.utils = {
hello: () => "Hello from middleware",
};
next();
});
Error Handling in Fastify
// Fastify error handler
fastify.setErrorHandler((error, request, reply) => {
fastify.log.error(error);
reply.code(error.statusCode || 500);
return { error: error.message };
});
// Custom error class
class ApiError extends Error {
constructor(
public statusCode: number,
message: string
) {
super(message);
}
}
fastify.get("/users/:id", async (request, reply) => {
const { id } = request.params as { id: string };
const user = await getUser(parseInt(id));
if (!user) {
throw new ApiError(404, "User not found");
}
return user;
});
Hooks System
// Request lifecycle hooks
fastify.addHook("onRequest", async (request, reply) => {
console.log("Request:", request.method, request.url);
});
fastify.addHook("preHandler", async (request, reply) => {
// Authentication
if (!request.headers.authorization) {
throw new Error("Unauthorized");
}
});
fastify.addHook("onSend", async (request, reply, payload) => {
// Modify response
return payload;
});
fastify.addHook("onResponse", async (request, reply) => {
console.log("Response:", reply.statusCode);
});
When to Use Fastify
✅ Use Fastify if:
- High throughput is critical
- You want built-in schema validation
- Building microservices
- Starting a new project
❌ Use Express if:
- Huge ecosystem matters
- Team expertise exists
- Maximum community support needed
- Migration cost too high
When to Use Express
✅ Use Express if:
- Team knows it well
- Large ecosystem of middleware
- Existing codebase
- Rapid prototyping
❌ Use Express if:
- Performance is critical
- You need modern design patterns
Real-World Benchmark
// Load test with k6
import http from "k6/http";
import { check } from "k6";
export const options = {
vus: 100, // 100 virtual users
duration: "30s",
};
export default function () {
const res = http.get("http://localhost:3000/");
check(res, {
"status is 200": (r) => r.status === 200,
"response time < 100ms": (r) => r.timings.duration < 100,
});
}
Results on modern hardware:
- Express: ~10,000 req/s
- Fastify: ~30,000 req/s
Migration Path
// Express code
app.post("/users", express.json(), (req, res) => {
res.json({ id: 1, ...req.body });
});
// Equivalent Fastify code
fastify.post("/users", async (request, reply) => {
return { id: 1, ...request.body };
});
FAQ
Q: Should I migrate my Express app to Fastify? A: Only if performance is bottlenecking. For most apps, Express is fine. If you're starting new, consider Fastify.
Q: Is Fastify mature for production? A: Yes, absolutely. Used by major companies at scale.
Q: Can I use Express middleware in Fastify? A: Partially, via @fastify/express. But native Fastify plugins are better.
Fastify is genuinely faster than Express. The question isn't speed but whether that speed matters for your use case. For new high-performance APIs, Fastify is the right choice. For existing Express apps, the migration cost rarely justifies the gains.
Advertisement