koa vs sails vs express vs hapi
Selecting a Node.js Web Framework for Backend Services
koasailsexpresshapiSimilar 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
koa5,588,06835,74565 kB3321 days agoMIT
sails27,09522,8433.27 MB589a month agoMIT
express068,88675.4 kB1914 months agoMIT
hapi014,783-587 years agoBSD-3-Clause

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: koa vs sails vs express vs hapi

  • 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.

  • 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.

README for koa

Koa middleware framework for nodejs

gitter NPM version build status Test coverage OpenCollective Backers OpenCollective Sponsors PR's Welcome

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.

Installation

Koa requires node v18.0.0 or higher for ES2015 and async function support.

npm install koa

Hello Koa

const Koa = require('koa');
const app = new Koa();

// response
app.use(ctx => {
  ctx.body = 'Hello Koa';
});

app.listen(3000);

Getting started

  • Kick-Off-Koa - An intro to Koa via a set of self-guided workshops.
  • Guide - Go straight to the docs.

Middleware

Koa is a middleware framework that can take two different kinds of functions as middleware:

  • async function
  • common function

Here is an example of logger middleware with each of the different functions:

async functions (node v7.6+)

app.use(async (ctx, next) => {
  const start = Date.now();
  await next();
  const ms = Date.now() - start;
  console.log(`${ctx.method} ${ctx.url} - ${ms}ms`);
});

Common function

// 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`);
  });
});

Koa v1.x Middleware Signature

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.

Context, Request and Response

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.

Koa Application

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.

Documentation

Troubleshooting

Check the Troubleshooting Guide or Debugging Koa in the general Koa guide.

Running tests

$ npm test

Reporting vulnerabilities

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.

Authors

See AUTHORS.

Community

Job Board

Looking for a career upgrade?

Backers

Support us with a monthly donation and help us continue our activities.

Sponsors

Become a sponsor and get your logo on our README on Github with a link to your site.

License

MIT