express, fastify, hyper-express, and koa are all server-side frameworks for Node.js that help developers build web applications and APIs. express is the most established option with a massive ecosystem of plugins and middleware. fastify focuses on high performance and low overhead, offering built-in validation and logging. koa is designed by the creators of Express to be more modern and modular, using async functions instead of callbacks. hyper-express aims to provide an Express-like interface with improved performance, often leveraging different underlying HTTP servers. Each tool solves the problem of routing and request handling but makes different trade-offs regarding speed, flexibility, and community support.
All four frameworks — express, fastify, hyper-express, and koa — help you build servers in Node.js. They handle routing, requests, and responses, but they differ in how they manage data flow, performance, and extensions. Let's look at how they handle real engineering tasks.
express uses a request-response middleware model.
req and res.next() to pass control to the next function.// express: Middleware chain
app.use((req, res, next) => {
console.log('Time:', Date.now());
next();
});
fastify uses an encapsulated plugin architecture.
next() callback pattern where possible.onRequest that are faster.// fastify: Hooks
fastify.addHook('onRequest', async (request, reply) => {
console.log('Time:', Date.now());
});
koa uses a context object and async functions.
req or res objects; everything is on ctx.await next() to flow through middleware.// koa: Context middleware
app.use(async (ctx, next) => {
console.log('Time:', Date.now());
await next();
});
hyper-express mimics Express but optimizes the underlying server.
req and res pattern for compatibility.// hyper-express: Express-like middleware
server.use((req, res, next) => {
console.log('Time:', Date.now());
next();
});
express handles validation via third-party middleware.
joi or express-validator.// express: External validation
app.post('/user', validationMiddleware, (req, res) => {
res.send(req.body);
});
fastify has schema validation built-in.
// fastify: Built-in schema
fastify.post('/user', {
schema: { body: { type: 'object', properties: { name: { type: 'string' } } } }
}, async (request, reply) => {
return request.body;
});
koa relies on community middleware for validation.
koa-bodyparser.// koa: External validation
app.use(bodyParser());
app.post('/user', async (ctx) => {
ctx.body = ctx.request.body;
});
hyper-express follows the Express pattern for validation.
// hyper-express: External validation
server.post('/user', validationMiddleware, (req, res) => {
res.send(req.body);
});
express uses a specific error-handling middleware signature.
(err, req, res, next).// express: Error handler
app.use((err, req, res, next) => {
res.status(500).send(err.message);
});
fastify handles errors via hooks or try/catch in handlers.
// fastify: Error handler
fastify.setErrorHandler((error, request, reply) => {
reply.status(500).send({ error: error.message });
});
koa uses a top-level try/catch in the app instance.
// koa: Error handler
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.status = 500;
ctx.body = err.message;
}
});
hyper-express mirrors Express error handling.
// hyper-express: Error handler
server.use((err, req, res, next) => {
res.status(500).send(err.message);
});
express has community-maintained types.
@types/express is standard but not perfect.// express: Type extension
interface Request {
user?: { id: string };
}
fastify is built with TypeScript in mind.
// fastify: Native types
fastify.get<{ Params: { id: string } }>('/user', async (req) => {
return req.params.id;
});
koa has good type support via @types/koa.
// koa: Context types
app.use(async (ctx: Koa.Context) => {
ctx.body = 'Hello';
});
hyper-express has limited type definitions.
// hyper-express: Basic types
server.use((req: Request, res: Response) => {
res.send('Hello');
});
express has the largest collection of plugins and tutorials.fastify is growing fast with a strong focus on quality.koa is stable but smaller than Express.hyper-express is niche with fewer resources.// Example: Finding middleware
// express: npm search 'express cors' yields many options
// fastify: npm search 'fastify cors' yields official plugin
fastify is engineered for speed and low overhead.hyper-express targets performance via underlying server swaps.express prioritizes stability over raw speed.koa is lighter than Express but not as fast as Fastify.// Example: Benchmarking setup
// All frameworks can be tested with autocannon or wrk
// Fastify typically leads in requests per second
express is mature with infrequent breaking changes.fastify follows semantic versioning strictly.koa is maintained by the Express team alumni.hyper-express has less visible long-term roadmaps.// Example: Version upgrades
// express: v4 to v5 took years, ensuring stability
// fastify: Regular major versions with clear migration paths
| Feature | express | fastify | koa | hyper-express |
|---|---|---|---|---|
| Architecture | Middleware (req/res) | Plugins & Hooks | Context (ctx) | Middleware (req/res) |
| Validation | External (3rd party) | Built-in (Schema) | External (3rd party) | External (3rd party) |
| Async Model | Callbacks / Promises | Async / Promises | Async / Await | Callbacks / Promises |
| TypeScript | Community Types | Native Types | Community Types | Limited Types |
| Performance | Standard | High | Medium | High |
express is the reliable workhorse 🐴. It is the default choice for most teams because it is easy to hire for and has a solution for everything. Use it for standard APIs where extreme performance is not the main bottleneck.
fastify is the speed demon 🏎️. It is the best choice for high-load microservices or when you want strong typing and validation without extra setup. It modernizes the Node.js server experience.
koa is the minimalist 🎨. It is perfect if you want to build your own framework on top of a solid async core. It removes legacy baggage but requires more decisions from you.
hyper-express is the specialist 🔧. Use it if you need Express compatibility but hit performance limits. Keep in mind the smaller community support when making this choice.
Final Thought: For most new projects today, fastify offers the best balance of speed and developer experience. However, express remains a safe and valid choice for teams prioritizing ecosystem size over raw performance.
Choose koa if you prefer a lightweight, modern core without the baggage of legacy callbacks. It is suitable for developers who want to build their own middleware stack from scratch. Good for projects that value clean architecture and async/await flow over out-of-the-box features.
Choose express if you need the largest ecosystem of middleware and tutorials. It is the safest bet for long-term stability and hiring developers who already know it. Ideal for standard REST APIs, legacy migrations, and projects where development speed matters more than raw performance.
Choose fastify if performance is a top priority or if you want built-in schema validation. It is excellent for microservices and high-throughput APIs. The plugin system is robust, and TypeScript support is first-class, making it great for large codebases.
Choose hyper-express if you need an Express-compatible API but require better performance for specific workloads like WebSockets. Be aware that the community is smaller, so finding third-party plugins or help might be harder. Best for niche cases where Express is too slow but you want similar syntax.
Expressive HTTP middleware framework for node.js to make web applications and APIs more enjoyable to write. Koa's middleware stack flows in a stack-like manner, allowing you to perform actions downstream then filter and manipulate the response upstream.
Only methods that are common to nearly all HTTP servers are integrated directly into Koa's small ~570 SLOC codebase. This includes things like content negotiation, normalization of node inconsistencies, redirection, and a few others.
Koa is not bundled with any middleware.
Koa requires node v18.0.0 or higher for ES2015 and async function support.
npm install koa
const Koa = require('koa');
const app = new Koa();
// response
app.use(ctx => {
ctx.body = 'Hello Koa';
});
app.listen(3000);
Koa is a middleware framework that can take two different kinds of functions as middleware:
Here is an example of logger middleware with each of the different functions:
app.use(async (ctx, next) => {
const start = Date.now();
await next();
const ms = Date.now() - start;
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
});
// Middleware normally takes two parameters (ctx, next), ctx is the context for one request,
// next is a function that is invoked to execute the downstream middleware. It returns a Promise with a then function for running code after completion.
app.use((ctx, next) => {
const start = Date.now();
return next().then(() => {
const ms = Date.now() - start;
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
});
});
The middleware signature changed between v1.x and v2.x. The older signature is deprecated.
Old signature middleware support has been removed in v3
Please see the Migration Guide from v2.x to v3.x for information on upgrading from v2.x to v3.x, and the Migration Guide from v1.x to v2.x for information on upgrading from v1.x to v2.x.
Each middleware receives a Koa Context object that encapsulates an incoming
http message and the corresponding response to that message. ctx is often used
as the parameter name for the context object.
app.use(async (ctx, next) => { await next(); });
Koa provides a Request object as the request property of the Context.
Koa's Request object provides helpful methods for working with
http requests which delegate to an IncomingMessage
from the node http module.
Here is an example of checking that a requesting client supports xml.
app.use(async (ctx, next) => {
ctx.assert(ctx.request.accepts('xml'), 406);
// equivalent to:
// if (!ctx.request.accepts('xml')) ctx.throw(406);
await next();
});
Koa provides a Response object as the response property of the Context.
Koa's Response object provides helpful methods for working with
http responses which delegate to a ServerResponse
.
Koa's pattern of delegating to Node's request and response objects rather than extending them
provides a cleaner interface and reduces conflicts between different middleware and with Node
itself as well as providing better support for stream handling. The IncomingMessage can still be
directly accessed as the req property on the Context and ServerResponse can be directly
accessed as the res property on the Context.
Here is an example using Koa's Response object to stream a file as the response body.
app.use(async (ctx, next) => {
await next();
ctx.response.type = 'xml';
ctx.response.body = fs.createReadStream('really_large.xml');
});
The Context object also provides shortcuts for methods on its request and response. In the prior
examples, ctx.type can be used instead of ctx.response.type and ctx.accepts can be used
instead of ctx.request.accepts.
For more information on Request, Response and Context, see the Request API Reference,
Response API Reference and Context API Reference.
The object created when executing new Koa() is known as the Koa application object.
The application object is Koa's interface with node's http server and handles the registration of middleware, dispatching to the middleware from http, default error handling, as well as configuration of the context, request and response objects.
Learn more about the application object in the Application API Reference.
Check the Troubleshooting Guide or Debugging Koa in the general Koa guide.
$ npm test
To report a security vulnerability, please do not open an issue, as this notifies attackers of the vulnerability. Instead, please email dead_horse, jonathanong, and niftylettuce to disclose.
See AUTHORS.
Looking for a career upgrade?
Support us with a monthly donation and help us continue our activities.
Become a sponsor and get your logo on our README on Github with a link to your site.