express vs hapi vs koa vs sails
Selecting a Node.js Web Framework for Backend Services
expresshapikoasailsSimilar Packages:

Selecting a Node.js Web Framework for Backend Services

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.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
express068,98175.4 kB2085 months agoMIT
hapi014,781-587 years agoBSD-3-Clause
koa035,70965 kB38a month agoMIT
sails022,8203.27 MB5912 months agoMIT

Express vs Hapi vs Koa vs Sails: Architecture and Usage Compared

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.

πŸ“‘ Request Handling: Middleware vs Configuration vs Context vs MVC

express uses a middleware function chain where you pass req and res objects.

  • You define logic in functions that call next() to pass control.
  • Simple to understand but relies on manual response handling.
// 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.

  • Logic is separated from transport details.
  • Requires more setup but enforces structure.
// 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.

  • No req or res passed around; everything is on ctx.
  • Cleaner syntax for async operations without callbacks.
// 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.

  • Code is organized into files like api/controllers/HomeController.js.
  • Built-in resolvers like res.ok() simplify responses.
// sails: Controller action
// api/controllers/HomeController.js
module.exports = {
  index: async function (req, res) {
    return res.ok('Hello World');
  }
};

🚨 Error Handling: Next(err) vs Try/Catch vs Centralized vs Resolvers

express relies on passing errors to next(err).

  • You must remember to call next with an error argument.
  • Requires a specific error-handling middleware signature.
// 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.

  • Errors are returned directly or thrown.
  • Framework catches unhandled errors automatically.
// 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.

  • No special error middleware needed for async errors.
  • Centralized error handling is still possible via middleware.
// 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.

  • Uses res.serverError() or similar helpers.
  • Errors can be handled globally in 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);
    }
  }
};

πŸ—ΊοΈ Routing: Manual vs Config vs External vs Blueprints

express defines routes directly on the app or router instance.

  • Flexible and explicit.
  • Easy to see all endpoints in one file.
// express: Direct routing
app.get('/users/:id', (req, res) => {
  res.json({ id: req.params.id });
});

hapi defines routes in a configuration array or object.

  • Decouples path definition from logic.
  • Supports nested routing via plugins.
// 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.

  • Keeps the core library small.
  • Adds an extra dependency for basic features.
// 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.

  • Can auto-generate routes based on controllers.
  • Highly opinionated structure.
// sails: config/routes.js
'GET /users/:id': 'HomeController.index',

// Or automatic blueprint routing based on controller name

🀝 Similarities: Shared Ground Between Frameworks

While the differences are clear, all four frameworks share core Node.js foundations. Here are key overlaps:

1. 🟒 Built on Node.js HTTP

  • All wrap the native http module.
  • Support standard HTTP methods (GET, POST, PUT, DELETE).
// All frameworks ultimately listen on a port
// express: app.listen(3000)
// hapi: await server.start()
// koa: app.listen(3000)
// sails: sails.lift({ port: 3000 })

2. πŸ”Œ Middleware and Plugins

  • All support extending functionality via third-party code.
  • Express and Koa use function-based middleware.
  • Hapi uses plugins with registration schemes.
  • Sails uses hooks and policies.
// 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

3. πŸ”’ Security Features

  • All support HTTPS, headers, and CORS configuration.
  • Hapi and Sails have more built-in protections by default.
  • Express and Koa require manual setup or middleware like helmet.
// Example: CORS setup
// express: app.use(cors())
// koa: app.use(cors())
// hapi: server.auth.strategy(...)
// sails: config/security.js

4. πŸ“¦ JSON API Support

  • All can return JSON responses easily.
  • Body parsing is built-in or easily added.
// 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 })

πŸ“Š Summary: Key Differences

Featureexpresshapikoasails
PhilosophyMinimal, unopinionatedConfiguration over codeModern, async-firstFull MVC framework
RoutingBuilt-inBuilt-in configExternal (koa-router)Built-in + Blueprints
Error Handlingnext(err)Try/catchTry/catchBuilt-in resolvers
Request Objectreq, resrequest, hctxreq, res
Learning CurveLowMediumMediumHigh
Best ForAPIs, MicroservicesEnterprise, ValidationPerformance, CustomRapid Prototyping, Real-time

πŸ’‘ The Big Picture

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.

How to Choose: express vs hapi vs koa vs sails

  • express:

    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.

  • hapi:

    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.

  • koa:

    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.

  • sails:

    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.

README for express

Express Logo

Fast, unopinionated, minimalist web framework for Node.js.

This project has a Code of Conduct.

Table of contents

NPM Version NPM Downloads Linux Build Test Coverage OpenSSF Scorecard Badge

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')
})

Installation

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.

Features

  • Robust routing
  • Focus on high performance
  • Super-high test coverage
  • HTTP helpers (redirection, caching, etc)
  • View system supporting 14+ template engines
  • Content negotiation
  • Executable for generating applications quickly

Docs & Community

PROTIP Be sure to read the migration guide to v5

Quick Start

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

Philosophy

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.

Examples

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

Contributing

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.

Security Issues

If you discover a security vulnerability in Express, please see Security Policies and Procedures.

Running Tests

To run the test suite, first install the dependencies:

npm install

Then run npm test:

npm test

Current project team members

For information about the governance of the express.js project, see GOVERNANCE.md.

The original author of Express is TJ Holowaychuk

List of all contributors

TC (Technical Committee)

TC emeriti members

TC emeriti members

Triagers

Triagers emeriti members

Emeritus Triagers

License

MIT