express, fastify, hapi, and koa are the four major server frameworks for Node.js, each solving the problem of handling HTTP requests with different architectural philosophies. express is the unopinionated industry standard with a massive middleware ecosystem. fastify focuses on speed and low overhead, using a schema-based approach for validation and serialization. hapi emphasizes configuration over code, offering robust built-in features for enterprise needs without relying on third-party middleware. koa, created by the Express team, modernizes the stack with async/await and a lightweight context object, leaving middleware choices entirely to the developer.
When building a backend in Node.js, the framework you pick shapes how you handle requests, validate data, and structure your code. express, fastify, hapi, and koa are the main contenders. They all route HTTP traffic, but they solve it in very different ways. Let's look at how they handle the daily work of an API developer.
express relies on a middleware chain. You add functions that run one after another. It is flexible but can get messy if you don't organize files well.
// express: Middleware chain
const express = require('express');
const app = express();
app.use((req, res, next) => {
console.log('Time:', Date.now());
next();
});
app.get('/user', (req, res) => {
res.send('User Data');
});
fastify uses a plugin architecture and hooks. It is designed for speed and encapsulates logic to avoid global state pollution.
// fastify: Plugin and Hooks
const fastify = require('fastify')();
fastify.addHook('onRequest', async (request, reply) => {
console.log('Incoming request');
});
fastify.get('/user', async (request, reply) => {
return { hello: 'world' };
});
hapi avoids middleware entirely. Instead, you configure extensions and routes with detailed options. It forces a structured approach.
// hapi: Configuration and Extensions
const Hapi = require('@hapi/hapi');
const server = Hapi.server({ port: 3000 });
server.ext('onRequest', (request, h) => {
console.log('Request started');
return h.continue;
});
server.route({
method: 'GET',
path: '/user',
handler: (request, h) => 'User Data'
});
koa uses a stack-like middleware system but with async/await. It gives you a clean ctx object instead of separate req and res.
// koa: Async Middleware Stack
const Koa = require('koa');
const app = new Koa();
app.use(async (ctx, next) => {
console.log('Time:', Date.now());
await next();
});
app.use(async (ctx) => {
ctx.body = 'User Data';
});
express has no built-in validation. You must install libraries like joi, zod, or express-validator and wire them up manually.
// express: Manual validation setup
const { body, validationResult } = require('express-validator');
app.post('/user',
body('email').isEmail(),
(req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) return res.status(400).json({ errors });
res.send('Created');
}
);
fastify has validation built-in using JSON Schema. It also serializes responses quickly based on the schema.
// fastify: Schema-based validation
fastify.post('/user', {
schema: {
body: {
type: 'object',
properties: { email: { type: 'string' } },
required: ['email']
}
}
}, async (request, reply) => {
return { id: 1, email: request.body.email };
});
hapi uses joi (often bundled or integrated) for rigorous validation rules defined in the route config.
// hapi: Joi validation in route options
const Joi = require('joi');
server.route({
method: 'POST',
path: '/user',
options: {
validate: {
payload: Joi.object({ email: Joi.string().required() })
}
},
handler: (request, h) => ({ id: 1, email: request.payload.email })
});
koa like Express, leaves validation to you. You pick your own library and middleware.
// koa: External validation middleware
const validator = require('koa-validator');
app.use(validator());
app.post('/user', async (ctx) => {
ctx.assert(ctx.request.body.email, 400, 'Email required');
ctx.body = { id: 1 };
});
express uses a special middleware function with four arguments. If you forget the next(err), the app hangs.
// express: Error middleware
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Something broke!');
});
fastify lets you set a global error handler or handle errors inside routes using try/catch.
// fastify: Global error handler
fastify.setErrorHandler((error, request, reply) => {
console.error(error);
reply.status(500).send({ error: 'Internal Server Error' });
});
hapi handles errors via the response toolkit. You return an error object or throw one, and the framework formats it.
// hapi: Error handling in handler
server.route({
method: 'GET',
path: '/error',
handler: (request, h) => {
throw new Error('Something broke');
// Or: return h.response().code(500);
}
});
koa uses a central error handler middleware. Since it uses promises, unhandled rejections are caught here.
// koa: Central error handler
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.status = err.status || 500;
ctx.body = err.message;
}
});
express is stable but older. It does not serialize JSON as fast as newer frameworks and has higher overhead per request.
// express: Standard JSON send
res.json({ message: 'Hello' });
// Uses JSON.stringify internally
fastify is built for speed. It uses fast-json-stringify to serialize responses up to 2x faster than standard JSON.
// fastify: Optimized serialization
reply.send({ message: 'Hello' });
// Uses schema-based serialization if defined
hapi prioritizes security and structure over raw speed. It is generally slower than Fastify but consistent.
// hapi: Standard response
return h.response({ message: 'Hello' });
// Robust but heavier processing
koa is lightweight and modern. It is faster than Express because it doesn't bundle legacy features, but it lacks Fastify's serialization tricks.
// koa: Body assignment
ctx.body = { message: 'Hello' };
// Standard JSON.stringify
express has the largest ecosystem. If a tool exists for Node, it likely has an Express middleware.
// express: Huge middleware variety
const cors = require('cors');
const helmet = require('helmet');
app.use(cors());
app.use(helmet());
fastify has a growing plugin ecosystem. Plugins are encapsulated, which prevents conflicts but requires learning the specific API.
// fastify: Encapsulated plugins
fastify.register(require('@fastify/cors'));
fastify.register(require('@fastify/helmet'));
hapi has a curated set of plugins. Quality is high, but variety is lower than Express.
// hapi: Official and community plugins
await server.register(require('@hapi/inert')); // Static assets
await server.register(require('@hapi/vision')); // Templates
koa shares much of the Express middleware ecosystem but requires wrappers for some older callback-based middleware.
// koa: Middleware compatibility
const cors = require('@koa/cors');
app.use(cors());
// Some express middleware needs 'koa-connect' wrapper
| Feature | express | fastify | hapi | koa |
|---|---|---|---|---|
| Style | Middleware | Plugin + Schema | Configuration | Async Middleware |
| Validation | External (Manual) | Built-in (JSON Schema) | Built-in (Joi) | External (Manual) |
| Error Handling | 4-arg Middleware | Handler / Try-Catch | Toolkit / Throw | Try-Catch Middleware |
| Performance | Standard | High (Optimized) | Standard | Lightweight |
| Learning Curve | Low | Medium | High | Medium |
| Best For | General Purpose | High Throughput | Enterprise | Modern Custom Stacks |
express is the reliable workhorse 🐴. It is everywhere, easy to learn, and gets the job done for 90% of projects. Use it when you want to spend time on business logic, not framework configuration.
fastify is the speed demon 🏎️. If you are building microservices that need to handle thousands of requests per second, or if you love schema-driven development, this is the modern choice.
hapi is the enterprise vault 🏦. It is strict and secure by default. Choose it if you are in a regulated industry or need a framework that enforces good patterns across a large team.
koa is the minimalist's canvas 🎨. It gives you the modern async features of Express without the legacy weight. Pick it if you want to build your own architecture from the ground up.
Final Thought: All four frameworks can build production-ready APIs. The decision comes down to whether you value ecosystem size (express), raw performance (fastify), strict governance (hapi), or modern minimalism (koa).
Choose express if you need the widest possible selection of third-party middleware, tutorials, and community support. It is the safest bet for standard REST APIs, prototypes, and teams that value convention and ease of hiring. Stick with it if you don't have strict performance requirements that demand micro-optimization.
Choose fastify if performance is a top priority or if you want built-in validation and serialization without adding extra libraries. It is ideal for high-throughput microservices and teams that prefer schema-driven development to catch errors early in the request lifecycle.
Choose hapi if you are building large-scale enterprise applications where security, input validation, and strict configuration are more important than flexibility. It suits teams that want a 'batteries-included' framework with strong governance and less reliance on the quality of external middleware.
Choose koa if you want a modern, lightweight foundation that uses async/await natively and gives you full control over middleware composition. It is best for developers who find Express callbacks cumbersome and want to build a custom stack without the baggage of legacy Express design decisions.
Fast, unopinionated, minimalist web framework for Node.js.
This project has a Code of Conduct.
import express from 'express'
const app = express()
app.get('/', (req, res) => {
res.send('Hello World')
})
app.listen(3000, () => {
console.log('Server is running on http://localhost:3000')
})
This is a Node.js module available through the npm registry.
Before installing, download and install Node.js. Node.js 18 or higher is required.
If this is a brand new project, make sure to create a package.json first with
the npm init command.
Installation is done using the
npm install command:
npm install express
Follow our installing guide for more information.
PROTIP Be sure to read the migration guide to v5
The quickest way to get started with express is to utilize the executable express(1) to generate an application as shown below:
Install the executable. The executable's major version will match Express's:
npm install -g express-generator@4
Create the app:
express /tmp/foo && cd /tmp/foo
Install dependencies:
npm install
Start the server:
npm start
View the website at: http://localhost:3000
The Express philosophy is to provide small, robust tooling for HTTP servers, making it a great solution for single page applications, websites, hybrids, or public HTTP APIs.
Express does not force you to use any specific ORM or template engine. With support for over 14 template engines via @ladjs/consolidate, you can quickly craft your perfect framework.
To view the examples, clone the Express repository:
git clone https://github.com/expressjs/express.git --depth 1 && cd express
Then install the dependencies:
npm install
Then run whichever example you want:
node examples/content-negotiation
The Express.js project welcomes all constructive contributions. Contributions take many forms, from code for bug fixes and enhancements, to additions and fixes to documentation, additional tests, triaging incoming pull requests and issues, and more!
See the Contributing Guide for more technical details on contributing.
If you discover a security vulnerability in Express, please see Security Policies and Procedures.
To run the test suite, first install the dependencies:
npm install
Then run npm test:
npm test
For information about the governance of the express.js project, see GOVERNANCE.md.
The original author of Express is TJ Holowaychuk