express vs fastify vs koa vs restify vs hapi
Choosing the Right Node.js Web Framework for Backend Services
expressfastifykoarestifyhapiSimilar Packages:

Choosing the Right Node.js Web Framework for Backend Services

express, fastify, hapi, koa, and restify are all popular Node.js frameworks used to build web servers and APIs, but they differ significantly in architecture, performance, and design philosophy. express is the most widely adopted, offering a minimal, unopinionated structure that relies heavily on middleware. fastify focuses on extreme speed and low overhead, featuring a built-in schema-based validation system. hapi provides a robust, configuration-driven approach with strong security defaults, often favored for large enterprise applications. koa is a modern, lightweight framework created by the Express team that uses async/await to eliminate callback hell, though it lacks built-in middleware. restify is specialized for building strict REST APIs, prioritizing performance and observability over general-purpose web features.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
express97,784,37969,45875.4 kB2289 months agoMIT
fastify9,518,05537,1182.95 MB1543 days agoMIT
koa5,133,42935,68465 kB374 months agoMIT
restify84,34110,688365 kB13219 days agoMIT
hapi53,25414,791-578 years agoBSD-3-Clause

Node.js Framework Showdown: Express, Fastify, Hapi, Koa, and Restify

When building backend services in Node.js, choosing the right framework sets the tone for your entire project architecture. While express, fastify, hapi, koa, and restify all handle HTTP requests, they solve problems in very different ways. This comparison breaks down their core mechanics, helping you decide which tool fits your specific engineering needs.

🏗️ Core Architecture: Minimalism vs. Structure

The biggest difference lies in how much the framework decides for you. Some give you a blank canvas, while others provide a rigid blueprint.

express is unopinionated. It gives you a simple app instance and lets you stack middleware however you like. You decide the folder structure, error handling strategy, and validation logic.

// express: Minimal setup, you add everything
const express = require('express');
const app = express();

app.use(express.json()); // You must manually add body parser

app.get('/hello', (req, res) => {
  res.send('Hello World');
});

fastify is also flexible but comes with more batteries included. It has a powerful plugin system and built-in validation, yet it keeps the core light.

// fastify: Built-in validation and serialization
const fastify = require('fastify')();

fastify.get('/hello', {
  schema: {
    response: { 200: { type: 'string' } }
  }
}, async (request, reply) => {
  return 'Hello World'; // Auto-serialized
});

hapi is highly opinionated. It forces a specific configuration style and separates concerns strictly. You define routes with detailed config objects, not just callbacks.

// hapi: Configuration-driven routes
const Hapi = require('@hapi/hapi');

const init = async () => {
  const server = Hapi.server({ port: 3000 });
  
  server.route({
    method: 'GET',
    path: '/hello',
    handler: (request, h) => {
      return 'Hello World';
    }
  });
  
  await server.start();
};

koa is a minimal kernel. It doesn't include routing or request body parsing. You build the middleware stack yourself using async functions.

// koa: No built-in router or body parser
const Koa = require('koa');
const app = new Koa();

app.use(async ctx => {
  ctx.body = 'Hello World';
});

restify is specialized for REST APIs. It skips HTML rendering features entirely and focuses on request handling pipelines.

// restify: Strictly for API endpoints
const restify = require('restify');
const server = restify.createServer();

server.get('/hello', (req, res, next) => {
  res.send('Hello World');
  return next();
});

⚡ Performance and Data Validation

Speed matters, especially for microservices. How a framework handles incoming data and validates it changes your code complexity.

express relies on third-party middleware for validation (like joi or zod). This adds flexibility but requires more setup code.

// express: Manual validation middleware
app.post('/user', (req, res, next) => {
  if (!req.body.email) {
    return res.status(400).send('Email required');
  }
  next();
});

fastify shines here. It uses JSON Schema to validate input and output automatically, which is faster than manual checks and reduces boilerplate.

// fastify: Schema-based validation
fastify.post('/user', {
  schema: {
    body: {
      type: 'object',
      required: ['email'],
      properties: { email: { type: 'string' } }
    }
  }
}, async (request, reply) => {
  return { status: 'created' }; // Validated automatically
});

hapi also has built-in validation (via joi integrated deeply). It validates params, query, payload, and headers before your handler even runs.

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

koa leaves validation entirely to you. You pick your own library and wrap it in middleware.

// koa: Custom validation middleware
app.use(async (ctx, next) => {
  if (ctx.request.body.email === undefined) {
    ctx.status = 400;
    ctx.body = 'Email required';
    return;
  }
  await next();
});

restify includes plugins for parsing and validation but is less focused on schema enforcement than Fastify or Hapi. It prioritizes raw throughput.

// restify: Using plugins for parsing
server.use(restify.plugins.bodyParser());
server.post('/user', (req, res, next) => {
  if (!req.params.email) {
    return next(new restify.BadRequestError('Email required'));
  }
  res.send(201);
  return next();
});

🔄 Control Flow: Callbacks vs. Async/Await

How the framework handles asynchronous logic affects code readability.

express traditionally uses the (req, res, next) callback pattern. While you can use async/await now, error handling still often relies on passing errors to next().

// express: Next() pattern for errors
app.get('/data', async (req, res, next) => {
  try {
    const data = await db.fetch();
    res.json(data);
  } catch (err) {
    next(err); // Must pass to error handler
  }
});

fastify embraces async/await fully. If you return a promise or throw an error, it handles the response automatically.

// fastify: Native async/await
fastify.get('/data', async (request, reply) => {
  const data = await db.fetch(); // No try/catch needed for 500s
  return data; // Auto-sent as JSON
});

hapi uses async functions for handlers. Errors thrown are automatically caught and converted to appropriate HTTP responses.

// hapi: Async handlers
handler: async (request, h) => {
  const data = await db.fetch();
  return data; // Errors caught automatically
}

koa was built for async/await from day one. It uses ctx (context) instead of separate req/res objects, making flow very clean.

// koa: Context-based async flow
app.use(async ctx => {
  ctx.body = await db.fetch(); // Clean and linear
});

restify uses the next() chain extensively. It is designed around middleware chains where every step must call next() to proceed.

// restify: Explicit next() chain
server.get('/data', [
  async (req, res, next) => {
    req.data = await db.fetch();
    return next();
  },
  (req, res, next) => {
    res.send(req.data);
    return next();
  }
]);

🛡️ Security and Error Handling

Security defaults vary wildly between these tools.

express has no security defaults. You must install helmet for headers, cors for cross-origin rules, and rate limiters manually.

// express: Manual security setup
const helmet = require('helmet');
app.use(helmet());
app.use(require('cors')());

fastify includes some security headers by default and makes it easy to add others via plugins. Its error handling is consistent via hooks.

// fastify: Global error hook
fastify.setErrorHandler((error, request, reply) => {
  reply.code(500).send({ error: 'Something went wrong' });
});

hapi is famous for security. It disables unnecessary features by default and requires explicit configuration for things like CORS and authentication.

// hapi: Explicit security config
await server.register(require('@hapi/inert')); // For static files
server.auth.strategy('jwt', 'jwt', { keys: 'secret' });

koa is barebones. Security is 100% your responsibility via middleware selection.

// koa: Import security middleware
app.use(require('koa-helmet')());

restify includes DDoS protection and rate limiting plugins out of the box, tailored for public APIs.

// restify: Built-in throttle
server.use(restify.plugins.throttle({
  burst: 100,
  rate: 50
}));

📊 Summary: Key Differences

Featureexpressfastifyhapikoarestify
PhilosophyMinimal, UnopinionatedHigh Performance, Schema-FirstConfiguration-Driven, SecureModern, Minimal KernelStrict REST API Focus
ValidationManual (3rd party)Built-in (JSON Schema)Built-in (Joi)Manual (3rd party)Plugin-based
Async StyleCallback / AsyncAsync / AwaitAsync / AwaitAsync / AwaitCallback Chain (next)
RoutingBuilt-inBuilt-inBuilt-inExternal (e.g., koa-router)Built-in
Best ForGeneral Web AppsMicroservices / High LoadEnterprise SystemsCustom Middleware StacksPublic REST APIs

💡 The Big Picture

express remains the safe bet for general-purpose web apps due to its vast ecosystem. If you need a tutorial for it, one exists. If you need a plugin, it's already written.

fastify is the modern choice for performance-critical services. If you are tired of writing validation boilerplate and want speed, this is the upgrade path.

hapi suits large teams needing strict governance. Its verbosity prevents sloppy code, making it ideal for banking or healthcare systems where structure saves lives.

koa is for purists who want to construct their own framework. It offers the cleanest async syntax but requires you to make every architectural decision.

restify is a specialist tool. Use it if you are building a public API gateway where observability and strict REST compliance matter more than flexibility.

Final Thought: There is no single "best" framework. express wins on community, fastify on speed, hapi on structure, koa on elegance, and restify on API specificity. Match the tool to your team's constraints and your project's scale.

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

  • express:

    Choose express if you need a mature, flexible framework with a massive ecosystem of plugins and community support. It is ideal for projects where rapid prototyping is key, or when your team is already familiar with its middleware pattern. However, be prepared to manually assemble security and validation tools, as the core is minimal.

  • fastify:

    Choose fastify if performance is your top priority and you want built-in features like JSON schema validation and logging without extra dependencies. It is perfect for high-throughput microservices where reducing latency and boilerplate code is critical. Its plugin architecture is robust, but the learning curve can be slightly steeper due to its specific lifecycle hooks.

  • koa:

    Choose koa if you want a modern, lightweight foundation that leverages async/await for cleaner control flow without the baggage of legacy callback patterns. It is best for developers who enjoy building their own middleware stack from scratch and value minimalism. Note that it does not include routing or body parsing by default, so you must select these tools yourself.

  • restify:

    Choose restify if you are strictly building RESTful APIs and need built-in support for versioning, content negotiation, and detailed observability. It is designed for production-grade services where strict adherence to REST principles and performance monitoring are non-negotiable. Do not use it for rendering HTML views or general-purpose web applications, as it lacks those features.

  • hapi:

    Choose hapi if you are building large-scale enterprise applications that require strong configuration management and security out of the box. It is suitable for teams that prefer an opinionated structure with clear separation of concerns over raw flexibility. Avoid it for small projects where its verbose configuration might feel like over-engineering.

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