Hono vs Fastify
Edge-first Hono versus Node.js Fastify: performance benchmarks, runtime support (Cloudflare Workers, Bun, Deno), middleware ecosystem, and DX.
The Node ecosystem's default: flexible, mature, a massive middleware pool
Schema-based, performance-focused: every route is a contract
Fastify's official benchmark (with an "illustrative" caveat) leads by 1.6× on throughput and 1.7× on latency, backed by schema-based serialization and the official OpenAPI plugin. Express 5's win is automatic error catching in async handlers. Express leads by a wide margin on GitHub (69,478 vs 37,195 stars). New, performance-critical service → Fastify; existing Express codebase + a familiar team → stay on Express 5, there's no official migration tool.
| Category | Express 5 | Fastify |
|---|---|---|
| Performance | 6/10 | 9/10 |
| Ease of Learning | 9/10 | 7/10 |
| Ecosystem | 9/10 | 7/10 |
| Community | 10/10 | 7/10 |
| Job Market | 8/10 | 6/10 |
| Future-Proof | 7/10 | 8/10 |
// Express 5 - Async route handler + automatic error catching
import express from 'express';
const app = express();
app.use(express.json());
// A simple error class — for a meaningful message at the API boundary
class NotFoundError extends Error {
status = 404;
}
async function getUser(id) {
const user = await db.users.findById(id);
if (!user) throw new NotFoundError('User not found');
return user;
}
// Express 5: if the async handler rejects/throws, next(err) is called automatically.
// Don't forget to return the Promise — otherwise the rejection can stay unhandled.
app.get('/api/users/:id', async (req, res) => {
const user = await getUser(req.params.id);
res.json(user);
});
// Central error middleware — added at the end of the global stack
app.use((err, req, res, next) => {
const status = err.status ?? 500;
res.status(status).json({ error: err.message });
});
app.listen(3000, () => {
console.log('Express server listening on port 3000');
});// Fastify 5 - Schema validation + automatic serialization
import Fastify from 'fastify';
const fastify = Fastify({ logger: true });
// Response schema: fast-json-stringify compiles a serialization
// function from this schema — faster than general-purpose JSON.stringify.
const userSchema = {
type: 'object',
properties: {
id: { type: 'string' },
name: { type: 'string' },
email: { type: 'string' },
},
required: ['id', 'name', 'email'],
};
const getUserOpts = {
schema: {
params: {
type: 'object',
properties: { id: { type: 'string' } },
required: ['id'],
},
response: {
200: userSchema,
},
},
};
// Encapsulation: hooks/decorators registered inside this plugin
// only leak into routes within this context.
fastify.register(async function userRoutes(instance) {
instance.get('/api/users/:id', getUserOpts, async (request, reply) => {
const user = await db.users.findById(request.params.id);
if (!user) {
return reply.code(404).send({ error: 'User not found' });
}
request.log.info({ userId: user.id }, 'user fetched');
return user;
});
});
fastify.setErrorHandler((error, request, reply) => {
reply.status(error.statusCode ?? 500).send({ error: error.message });
});
await fastify.listen({ port: 3000 });Fastify's official benchmark (with an "illustrative" caveat) leads by 1.6× on throughput and 1.7× on latency, backed by schema-based serialization and the official OpenAPI plugin. Express 5's win is automatic error catching in async handlers. Express leads by a wide margin on GitHub (69,478 vs 37,195 stars). New, performance-critical service → Fastify; existing Express codebase + a familiar team → stay on Express 5, there's no official migration tool.
Get Free ConsultationIf you're building a new, performance-critical, schema-validated service, go with Fastify; if you have existing Express investment and a familiar team, go with Express 5. Both are actively maintained — on the npm registry, express@latest is 5.2.1 and fastify@latest is 5.12.5 (Fastify v6 is in alpha, but the stable line is still 5.x) — so don't decide on the basis that "Express is dead." If you need a lot of third-party middleware, the scale tips further toward Express.