express, fastify, koa, and next-connect are all tools used to build web servers and handle HTTP requests in Node.js, but they serve different architectural needs. express is the long-standing standard, known for its massive ecosystem and simplicity, making it the default choice for many traditional APIs. fastify focuses heavily on speed and low overhead, using a schema-based approach to validate data quickly. koa was built by the creators of Express to be more modern and lightweight, relying on async functions and middleware composition without built-in request/response objects. next-connect is distinct because it is not a standalone server; it is a minimal layer that brings Express-like middleware support specifically to Next.js API routes, allowing developers to reuse middleware patterns within the Next.js ecosystem.
When building a backend in Node.js, the framework you choose shapes how you handle requests, manage errors, and scale your application. express, fastify, koa, and next-connect all solve the problem of routing HTTP traffic, but they do it with very different philosophies. Let's look at how they handle real-world engineering challenges.
express gives you a lot out of the box but relies on external middleware for many modern features.
// express: Minimal setup with external parser
const express = require('express');
const app = express();
// Must explicitly add middleware to parse JSON
app.use(express.json());
app.get('/user', (req, res) => {
res.json({ id: 1 });
});
fastify comes with high-performance features built-in, like JSON parsing and logging.
// fastify: Built-in parsing and schema
const fastify = require('fastify')({ logger: true });
fastify.get('/user', {
schema: {
response: {
200: {
type: 'object',
properties: { id: { type: 'number' } }
}
}
}
}, async (request, reply) => {
return { id: 1 }; // Automatically serialized
});
koa provides almost nothing except the core middleware mechanism.
// koa: Minimal core, requires external router/parser
const Koa = require('koa');
const Router = require('@koa/router');
const bodyParser = require('koa-bodyparser');
const app = new Koa();
const router = new Router();
app.use(bodyParser());
router.get('/user', (ctx) => {
ctx.body = { id: 1 };
});
app.use(router.routes());
next-connect is not a server itself; it connects middleware to Next.js.
// next-connect: Middleware chain in Next.js
import nextConnect from 'next-connect';
import cors from 'cors';
const handler = nextConnect();
handler.use(cors()); // Reuse Express middleware
handler.get((req, res) => {
res.json({ id: 1 });
});
export default handler; // Export for Next.js API route
How the framework handles the flow of code between steps (middleware) is a major difference.
express uses a callback-based chain where you must call next() to proceed.
next(), the request hangs forever.// express: Explicit next() call required
app.use((req, res, next) => {
console.log('Time:', Date.now());
next(); // Must call this to continue
});
app.use((err, req, res, next) => {
// Special error handler signature
res.status(500).send(err.message);
});
koa uses modern async/await and a "downstream-upstream" flow.
await next() to pause and resume around downstream middleware.// koa: Async/await composition
app.use(async (ctx, next) => {
const start = Date.now();
await next(); // Wait for downstream to finish
const ms = Date.now() - start;
console.log(`Response time: ${ms}ms`);
});
fastify also uses async/await but encapsulates logic within route handlers and hooks.
// fastify: Lifecycle hooks instead of generic middleware
fastify.addHook('onRequest', async (request, reply) => {
console.log('Request received');
// No need to call next(); hooks resolve automatically
});
fastify.get('/user', async (request, reply) => {
return { id: 1 };
});
next-connect mimics the Express middleware signature but runs in a Promise chain.
async middleware functions without needing next() if you return a response.// next-connect: Async middleware support
handler.use(async (req, res, next) => {
if (!req.user) {
return res.status(401).end('Unauthorized'); // Return early
}
await next(); // Continue if authorized
});
express does not validate data by default.
Joi or Zod manually.// express: Manual validation setup
app.post('/user', (req, res) => {
if (!req.body.email) {
return res.status(400).json({ error: 'Email required' });
}
// Process logic...
});
fastify compiles validation schemas into highly optimized functions.
// fastify: Schema-based validation
fastify.post('/user', {
schema: {
body: {
type: 'object',
required: ['email'],
properties: { email: { type: 'string' } }
}
}
}, async (request, reply) => {
// request.body is guaranteed to be valid here
return { status: 'created' };
});
koa leaves performance optimization up to you.
// koa: Choose your own validation strategy
router.post('/user', async (ctx) => {
// Example using a custom validator
const { email } = ctx.request.body;
if (!email) ctx.throw(400, 'Email required');
ctx.body = { status: 'created' };
});
next-connect inherits the performance characteristics of the underlying Next.js runtime.
// next-connect: Pluggable validation
import { validate } from './my-validator';
handler.post(validate, (req, res) => {
// Logic runs only if validation passes
res.json({ status: 'created' });
});
express, fastify, and koa are designed to run as standalone servers.
node server.js or inside a Docker container.// express/koa/fastify: Standalone server pattern
const port = 3000;
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
next-connect is designed for serverless environments managed by Next.js.
listen(); Next.js handles the incoming requests.// next-connect: No listen() call
// The function is exported directly to handle a request
export default handler;
Despite their differences, these tools share common goals and patterns.
// All frameworks support basic routing
// Express
app.get('/hello', (req, res) => res.send('Hi'));
// Fastify
fastify.get('/hello', async () => 'Hi');
// Koa
router.get('/hello', (ctx) => { ctx.body = 'Hi'; });
// Next-Connect
handler.get((req, res) => res.send('Hi'));
cors or helmet).// Using 'cors' middleware across compatible frameworks
// Express
app.use(cors());
// Koa
app.use(cors());
// Next-Connect
handler.use(cors());
// Centralized error handling concept
// Express: app.use((err, req, res, next) => ...)
// Koa: app.on('error', ...)
// Fastify: fastify.setErrorHandler((err, request, reply) => ...)
// Next-Connect: handler.onError((err, req, res) => ...)
| Feature | express | fastify | koa | next-connect |
|---|---|---|---|---|
| Primary Use | General purpose API server | High-performance microservices | Lightweight custom servers | Next.js API routes |
| Async Model | Callbacks (next()) | Async/Await + Hooks | Async/Await (await next()) | Async/Await Promise Chain |
| Validation | Manual (external libs) | Built-in (JSON Schema) | Manual (external libs) | Manual (via middleware) |
| Ecosystem | Massive | Growing, plugin-focused | Modular, pick-your-own | Compatible with Express libs |
| Deployment | Standalone / Docker | Standalone / Docker | Standalone / Docker | Serverless (Vercel/Next.js) |
express is the safe, reliable choice π‘οΈ. If you need a framework that "just works" with any tutorial or library you find, this is it. It is perfect for standard business applications where developer familiarity matters more than raw speed.
fastify is the performance powerhouse π. Choose this when you are building high-load services where every millisecond counts, or when you want strict data validation enforced by the framework itself.
koa is the minimalist's canvas π¨. It is best for experienced teams who want to build a tailored architecture without the weight of legacy decisions. It offers the cleanest code flow for complex async operations.
next-connect is the specialized bridge π. Use it only if you are already in the Next.js ecosystem and miss the simplicity of Express middleware in your API routes. It is not a replacement for a full server but a tool to make Next.js API development smoother.
Final Thought: The "best" framework depends on where your code will run and what your team values. If you need raw speed and structure, go with fastify. If you need ecosystem breadth, express remains king. If you are building on Next.js, next-connect saves you from reinventing the wheel.
Choose fastify when performance is a critical requirement, such as in high-throughput microservices or real-time data processing. It is ideal if you want built-in validation and serialization using JSON Schema, which reduces the need for extra libraries. Select this if your team is comfortable defining schemas for routes and wants a framework that encourages best practices for speed and security out of the box.
Choose express if you need a stable, battle-tested framework with the largest ecosystem of plugins and tutorials available. It is the best fit for teams that value convention over configuration and need to integrate with legacy systems or third-party tools that expect an Express environment. While it may not be the fastest option, its maturity reduces risk for standard REST APIs and monolithic server applications.
Choose koa if you prefer a lightweight, modern foundation where you assemble your own architecture from the ground up. It is suitable for developers who want full control over middleware flow using async/await without the baggage of older callback-based patterns. Pick this for projects where you need a minimal core and are willing to select your own router, validation, and body parsing libraries.
Choose next-connect exclusively if you are building a Next.js application and need to use Express-style middleware inside your API routes or serverless functions. It is the right tool when you want to avoid running a separate Express server and instead leverage Next.js built-in deployment features while keeping your middleware logic clean and reusable. Do not use this for standalone servers outside of the Next.js environment.
An efficient server implies a lower cost of the infrastructure, better responsiveness under load, and happy users. How can you efficiently handle the resources of your server, knowing that you are serving the highest number of requests possible, without sacrificing security validations and handy development?
Enter Fastify. Fastify is a web framework highly focused on providing the best developer experience with the least overhead and a powerful plugin architecture. It is inspired by Hapi and Express and as far as we know, it is one of the fastest web frameworks in town.
The main branch refers to the Fastify v5 release.
Check out the 4.x branch for v4.
Create a folder and make it your current working directory:
mkdir my-app
cd my-app
Generate a Fastify project with npm init:
npm init fastify
Install dependencies:
npm i
To start the app in dev mode:
npm run dev
For production mode:
npm start
Under the hood npm init downloads and runs Fastify
Create, which in turn uses the
generate functionality of Fastify CLI.
To install Fastify in an existing project as a dependency:
npm i fastify
// Require the framework and instantiate it
// ESM
import Fastify from 'fastify'
const fastify = Fastify({
logger: true
})
// CommonJS
const fastify = require('fastify')({
logger: true
})
// Declare a route
fastify.get('/', (request, reply) => {
reply.send({ hello: 'world' })
})
// Run the server!
fastify.listen({ port: 3000 }, (err, address) => {
if (err) throw err
// Server is now listening on ${address}
})
With async-await:
// ESM
import Fastify from 'fastify'
const fastify = Fastify({
logger: true
})
// CommonJS
const fastify = require('fastify')({
logger: true
})
fastify.get('/', async (request, reply) => {
reply.type('application/json').code(200)
return { hello: 'world' }
})
fastify.listen({ port: 3000 }, (err, address) => {
if (err) throw err
// Server is now listening on ${address}
})
Do you want to know more? Head to the Getting Started.
If you learn best by reading code, explore the official demo.
Note
.listenbinds to the local host,localhost, interface by default (127.0.0.1or::1, depending on the operating system configuration). If you are running Fastify in a container (Docker, GCP, etc.), you may need to bind to0.0.0.0. Be careful when listening on all interfaces; it comes with inherent security risks. See the documentation for more information.
Machine: EX41S-SSD, Intel Core i7, 4Ghz, 64GB RAM, 4C/8T, SSD.
Method: autocannon -c 100 -d 40 -p 10 localhost:3000 * 2, taking the
second average
| Framework | Version | Router? | Requests/sec |
|---|---|---|---|
| Express | 4.17.3 | β | 14,200 |
| hapi | 20.2.1 | β | 42,284 |
| Restify | 8.6.1 | β | 50,363 |
| Koa | 2.13.0 | β | 54,272 |
| Fastify | 4.0.0 | β | 77,193 |
| - | |||
http.Server | 16.14.2 | β | 74,513 |
These benchmarks were taken using https://github.com/fastify/benchmarks. This is a synthetic "hello world" benchmark that aims to evaluate the framework overhead. The overhead that each framework has on your application depends on your application. You should always benchmark if performance matters to you.
Getting StartedGuidesServerRoutesEncapsulationLoggingMiddlewareHooksDecoratorsValidation and SerializationFluent SchemaLifecycleReplyRequestErrorsContent Type ParserPluginsTestingBenchmarkingHow to write a good pluginPlugins GuideHTTP2Long Term SupportTypeScript and types supportServerlessRecommendationsPlease visit Fastify help to view prior support issues and to ask new support questions.
Version 3 of Fastify and lower are EOL and will not receive any security or bug fixes.
Fastify's partner, HeroDevs, provides commercial security fixes for all unsupported versions at https://herodevs.com/support/fastify-nes. Fastify's supported version matrix is available in the Long Term Support documentation.
Whether reporting bugs, discussing improvements and new ideas, or writing code, we welcome contributions from anyone and everyone. Please read the CONTRIBUTING guidelines before submitting pull requests.
Fastify is the result of the work of a great community. Team members are listed in alphabetical order.
Lead Maintainers:
Great contributors to a specific area of the Fastify ecosystem will be invited to join this group by Lead Maintainers when they decide to step down from the active contributor's group.
We are an At-Large Project in the OpenJS Foundation.
Support this project by becoming a SPONSOR! Fastify has an Open Collective page where we accept and manage financial contributions.
This project is kindly sponsored by:
This list includes all companies that support one or more team members in maintaining this project.
Licensed under MIT.
For your convenience, here is a list of all the licenses of our production dependencies: