express, hapi, koa, and sails are all server-side frameworks for Node.js that handle HTTP requests, routing, and middleware, but they differ significantly in architecture and philosophy. express is the minimal standard, offering unopinionated routing and middleware support. hapi (now @hapi/hapi) focuses on configuration over code with built-in validation and security. koa is built by the Express team to be more modern and lightweight, using async functions and context objects instead of request/response pairs. sails is a full MVC framework built on Express, providing an ORM, blueprints, and WebSocket support out of the box.
All four frameworks enable you to build web servers in Node.js, but they solve common problems in very different ways. express is the minimal standard, hapi is configuration-heavy, koa is modern and async-focused, and sails is a full MVC suite. Let's compare how they handle core engineering tasks.
express uses a middleware function chain where you pass req and res objects.
next() to pass control.// express: Middleware chain
const express = require('express');
const app = express();
app.use((req, res, next) => {
console.log('Time:', Date.now());
next();
});
app.get('/', (req, res) => {
res.send('Hello World');
});
hapi uses a configuration object to define routes and handlers.
// hapi: Configuration based
const Hapi = require('@hapi/hapi');
const init = async () => {
const server = Hapi.server({ port: 3000, host: 'localhost' });
server.route({
method: 'GET',
path: '/',
handler: (request, h) => {
return 'Hello World';
}
});
await server.start();
};
koa uses async functions and a single ctx (context) object.
req or res passed around; everything is on ctx.// koa: Async context
const Koa = require('koa');
const app = new Koa();
app.use(async ctx => {
ctx.body = 'Hello World';
});
app.listen(3000);
sails uses controllers and actions within an MVC structure.
api/controllers/HomeController.js.res.ok() simplify responses.// sails: Controller action
// api/controllers/HomeController.js
module.exports = {
index: async function (req, res) {
return res.ok('Hello World');
}
};
express relies on passing errors to next(err).
next with an error argument.// express: Error middleware
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Something broke!');
});
hapi uses standard try/catch blocks within handlers.
// hapi: Try/catch in handler
server.route({
method: 'GET',
path: '/',
handler: async (request, h) => {
try {
const data = await getData();
return data;
} catch (err) {
throw err; // Handled by hapi
}
}
});
koa leverages native async/await try/catch.
// koa: Native try/catch
app.use(async (ctx, next) => {
try {
await next();
} catch (err) {
ctx.status = err.status || 500;
ctx.body = err.message;
}
});
sails provides built-in error responses and policies.
res.serverError() or similar helpers.config/responses.js.// sails: Built-in error response
module.exports = {
index: async function (req, res) {
try {
await SomeService.doWork();
return res.ok();
} catch (err) {
return res.serverError(err);
}
}
};
express defines routes directly on the app or router instance.
// express: Direct routing
app.get('/users/:id', (req, res) => {
res.json({ id: req.params.id });
});
hapi defines routes in a configuration array or object.
// hapi: Route config
server.route({
method: 'GET',
path: '/users/{id}',
handler: (request, h) => {
return { id: request.params.id };
}
});
koa does not include routing by default; you install koa-router.
// koa: External router
const Router = require('koa-router');
const router = new Router();
router.get('/users/:id', ctx => {
ctx.body = { id: ctx.params.id };
});
app.use(router.routes());
sails uses a config/routes.js file or automatic blueprints.
// sails: config/routes.js
'GET /users/:id': 'HomeController.index',
// Or automatic blueprint routing based on controller name
While the differences are clear, all four frameworks share core Node.js foundations. Here are key overlaps:
http module.// All frameworks ultimately listen on a port
// express: app.listen(3000)
// hapi: await server.start()
// koa: app.listen(3000)
// sails: sails.lift({ port: 3000 })
// Example: Logging middleware concept
// express: app.use(logger())
// koa: app.use(logger())
// hapi: await server.register(require('hapi-pino'))
// sails: config.http.middleware order includes logger
helmet.// Example: CORS setup
// express: app.use(cors())
// koa: app.use(cors())
// hapi: server.auth.strategy(...)
// sails: config/security.js
// Example: Sending JSON
// express: res.json({ data: 1 })
// hapi: return h.response({ data: 1 })
// koa: ctx.body = { data: 1 }
// sails: return res.json({ data: 1 })
| Feature | express | hapi | koa | sails |
|---|---|---|---|---|
| Philosophy | Minimal, unopinionated | Configuration over code | Modern, async-first | Full MVC framework |
| Routing | Built-in | Built-in config | External (koa-router) | Built-in + Blueprints |
| Error Handling | next(err) | Try/catch | Try/catch | Built-in resolvers |
| Request Object | req, res | request, h | ctx | req, res |
| Learning Curve | Low | Medium | Medium | High |
| Best For | APIs, Microservices | Enterprise, Validation | Performance, Custom | Rapid Prototyping, Real-time |
express is the safe choice π‘οΈ β it has the most resources, hires are easy to find, and it gets the job done without fuss. Ideal for most standard APIs and web services.
hapi is the structured choice π β great for large teams that need strict validation and configuration rules. Best when security and input validation are top priorities.
koa is the modern choice π β perfect for developers who want clean async code and don't mind assembling their own tools. Best for high-performance services where every millisecond counts.
sails is the complete choice ποΈ β suited for teams that want a Rails-like experience in Node.js. Best for startups needing real-time features and database models out of the box.
Final Thought: All four frameworks are capable production tools. Your choice should depend on how much structure you want imposed on your team versus how much freedom you need to build your own architecture.
Choose express if you want the industry standard with the largest ecosystem of middleware and tutorials. It is ideal for teams that need flexibility without strict rules, allowing you to build your architecture from the ground up. It works well for APIs, microservices, and server-rendered views where you want control over every layer.
Choose hapi if you prefer configuration over code and need built-in input validation and security features. It is suitable for large enterprise applications where strict structure and plugin isolation are required. Note that the package scope changed to @hapi/hapi in recent versions, so ensure your team is aware of the migration path.
Choose koa if you want a modern, lightweight foundation that leverages async/await without callback hell. It is best for teams that want to build their own middleware stack from scratch without the baggage of older Node.js patterns. It shines in high-performance scenarios where you need fine-grained control over the request context.
Choose sails if you need a full-featured MVC framework similar to Ruby on Rails or Django. It is perfect for rapid prototyping, real-time apps with WebSocket support, and projects that benefit from a built-in ORM and blueprints. Avoid it if you prefer minimal dependencies or want to pick each library yourself.
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