express vs fastify vs hapi vs koa vs sails
Selecting a Node.js Web Framework for Backend-for-Frontend and API Services
expressfastifyhapikoasails

Selecting a Node.js Web Framework for Backend-for-Frontend and API Services

express, fastify, hapi, koa, and sails are all server-side frameworks for Node.js used to build web applications and APIs. express is the minimal and widely adopted standard. fastify focuses on high performance and developer experience with built-in validation. hapi emphasizes configuration over code and is suited for enterprise needs. koa is a lightweight foundation built by the Express team that leverages async/await. sails is an opinionated MVC framework that auto-generates REST APIs.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
express72,770,92168,88075.4 kB1893 months agoMIT
fastify4,762,01335,8212.77 MB1097 days agoMIT
hapi014,783-587 years agoBSD-3-Clause
koa035,74865 kB2817 days agoMIT
sails022,8433.27 MB58925 days agoMIT

Node.js Web Frameworks: Express vs Fastify vs Hapi vs Koa vs Sails

When building a Backend-for-Frontend (BFF) or a standalone API in Node.js, the framework you choose shapes your development speed, runtime performance, and long-term maintenance. express, fastify, hapi, koa, and sails all solve the same core problem β€” handling HTTP requests β€” but they approach it with different philosophies. Let's compare how they handle routing, middleware, validation, and architecture.

πŸ›£οΈ Routing and Request Handling

The way you define routes sets the tone for your entire codebase. Some frameworks prefer code, while others prefer configuration.

express uses a straightforward, code-based approach where you attach methods directly to the app instance.

// express: Direct method attachment
const express = require('express');
const app = express();

app.get('/users', (req, res) => {
  res.json({ users: [] });
});

fastify looks similar to Express but wraps the request and reply objects for better performance and encapsulation.

// fastify: Encapsulated request/reply
const fastify = require('fastify')();

fastify.get('/users', async (request, reply) => {
  return { users: [] };
});

hapi separates route definitions from the server instance using a configuration object.

// hapi: Configuration-based routes
const Hapi = require('@hapi/hapi');
const server = Hapi.server({ port: 3000 });

server.route({
  method: 'GET',
  path: '/users',
  handler: (request, h) => {
    return { users: [] };
  }
});

koa does not have a built-in router. You must install @koa/router separately, and it uses a middleware stack approach.

// koa: Middleware stack with external router
const Koa = require('koa');
const Router = require('@koa/router');
const app = new Koa();
const router = new Router();

router.get('/users', (ctx) => {
  ctx.body = { users: [] };
});

app.use(router.routes());

sails relies heavily on convention. Routes are often auto-generated based on your controllers and models, or defined in a config file.

// sails: config/routes.js mapping
module.exports.routes = {
  'GET /users': 'UserController.find'
};

// UserController.js
module.exports = {
  find: async function (req, res) {
    return res.json({ users: [] });
  }
};

πŸ”Œ Middleware and Plugin Architecture

Middleware determines how you handle cross-cutting concerns like logging, authentication, and parsing.

express uses a linear stack. Middleware functions receive req, res, and next.

// express: Linear middleware stack
app.use((req, res, next) => {
  console.log('Time:', Date.now());
  next();
});

fastify uses a hook-based system (onRequest, preHandler) which is faster and avoids the next() callback pattern.

// fastify: Lifecycle hooks
fastify.addHook('onRequest', async (request, reply) => {
  console.log('Request received');
});

hapi uses extension points that are similar to hooks but strictly configured within the server lifecycle.

// hapi: Server extensions
server.ext('onRequest', (request, h) => {
  console.log('Request received');
  return h.continue;
});

koa uses a composed middleware stack where you call next() as a promise, enabling clean async/await flows.

// koa: Composed async middleware
app.use(async (ctx, next) => {
  await next();
  console.log('Response sent');
});

sails uses a policy system for middleware-like logic, often applied globally or per-controller.

// sails: Policies (config/policies.js)
module.exports.policies = {
  UserController: {
    find: ['isAuthenticated']
  }
};

βœ… Validation and Schema

Validating input is critical for security. Some frameworks require external libraries, while others have it built-in.

express has no built-in validation. You typically add joi, zod, or express-validator.

// express: External validation middleware
const { body, validationResult } = require('express-validator');

app.post('/users', 
  body('email').isEmail(), 
  (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) return res.status(400).json(errors);
  }
);

fastify has schema-based validation built-in using JSON Schema.

// fastify: Built-in JSON Schema validation
fastify.post('/users', {
  schema: {
    body: {
      type: 'object',
      properties: {
        email: { type: 'string', format: 'email' }
      }
    }
  }
}, async (request, reply) => {
  // Body is already validated
});

hapi uses joi (now hapi/joi or joi standalone) integrated deeply into the route validation options.

// hapi: Integrated Joi validation
server.route({
  method: 'POST',
  path: '/users',
  options: {
    validate: {
      payload: {
        email: Joi.string().email().required()
      }
    }
  },
  handler: (request, h) => { /*...*/ }
});

koa like Express, requires external middleware for validation.

// koa: External validation middleware
const validate = require('koa-json-validator');

router.post('/users', validate({
  properties: {
    email: { type: 'string' }
  }
}), (ctx) => { /*...*/ });

sails includes validation rules directly in your model definitions.

// sails: Model-based validation (api/models/User.js)
module.exports = {
  attributes: {
    email: {
      type: 'string',
      isEmail: true,
      required: true
    }
  }
};

⚑ Performance and Concurrency

Runtime speed matters when your API scales. fastify is generally recognized as the fastest in this group due to its low-overhead architecture. express is solid but carries legacy overhead. hapi is robust but heavier due to its validation and configuration layers. koa is lightweight but depends on what middleware you add. sails is the heaviest as it loads a full MVC stack including ORM and WebSocket support by default.

For high-throughput microservices, fastify often handles 2x-3x more requests per second than express in benchmark scenarios. koa sits in the middle, offering good performance if you keep the middleware stack lean.

πŸ› οΈ Maintenance and Ecosystem Status

express is in maintenance mode. It is stable and unlikely to change, which is good for longevity but means new features are rare. The ecosystem is massive.

fastify is actively developed with frequent releases. It has a growing plugin ecosystem and strong TypeScript support.

hapi is stable and enterprise-focused. It has a smaller community than Express but is well-maintained by its core team.

koa is stable with infrequent updates. It is a foundation rather than a full framework, so you rely on third-party middleware quality.

sails has a slower release cadence. While still functional, it is less common in new greenfield projects compared to modern alternatives like NestJS or Fastify.

πŸ“Š Summary Table

Featureexpressfastifyhapikoasails
PhilosophyMinimal, UnopinionatedHigh Performance, Schema-basedConfiguration, EnterpriseMinimal, Async/AwaitMVC, Convention over Config
RoutingCode-basedCode-basedConfig-basedExternal ModuleAuto-generated / Config
ValidationExternal MiddlewareBuilt-in (JSON Schema)Built-in (Joi)External MiddlewareModel-based
Async ModelCallbacks / PromisesPromises / AsyncPromises / AsyncAsync / AwaitAsync / Await
Learning CurveLowLow/MediumMediumMediumHigh
Best ForGeneral Purpose, LegacyMicroservices, High LoadEnterprise, Complex AppsCustom ArchitecturesRapid CRUD Prototyping

πŸ’‘ The Big Picture

express remains the safe choice for general-purpose APIs where library support is critical. It is the default for a reason β€” it works everywhere.

fastify is the modern upgrade. If you are starting a new service today, especially a microservice, its built-in validation and speed make it a strong candidate.

hapi shines in large organizations where configuration rules and strict validation prevent errors before they happen.

koa is for architects who want to build their own framework on top of a solid async foundation without the baggage of older callback patterns.

sails is a specialized tool for teams that want a Rails-like experience in Node.js, complete with auto-generated APIs and WebSocket support, but it comes with more weight.

Final Thought: For most frontend teams building BFF layers, fastify offers the best balance of speed and developer experience, while express remains the reliable workhorse for integrating with existing tools. Choose based on how much structure you want out of the box versus how much you want to build yourself.

How to Choose: express vs fastify vs hapi vs koa vs sails

  • express:

    Choose express if you need maximum compatibility with existing middleware, extensive community support, and a simple learning curve. It is the safest bet for legacy projects or when you need a specific plugin that only exists in the Express ecosystem.

  • fastify:

    Choose fastify if performance is a priority or you want built-in schema validation to reduce boilerplate. It is ideal for microservices and high-throughput APIs where low latency matters.

  • hapi:

    Choose hapi if you prefer configuration over code and need robust input validation and security policies out of the box. It fits well in large enterprise teams that value strict structure and stability.

  • koa:

    Choose koa if you want a minimal core without callback hell and prefer building your own architecture from the ground up using modern async/await features. It is best for developers who want full control over middleware stacking.

  • sails:

    Choose sails if you want a full MVC framework that automatically generates REST APIs from your data models. It is suitable for rapid prototyping of standard CRUD applications but may feel heavy for custom microservices.

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