express vs fastify vs hapi vs koa
Building High-Performance Node.js APIs
expressfastifyhapikoaSimilar Packages:

Building High-Performance Node.js APIs

express, fastify, hapi, and koa are the four major server frameworks for Node.js, each solving the problem of handling HTTP requests with different architectural philosophies. express is the unopinionated industry standard with a massive middleware ecosystem. fastify focuses on speed and low overhead, using a schema-based approach for validation and serialization. hapi emphasizes configuration over code, offering robust built-in features for enterprise needs without relying on third-party middleware. koa, created by the Express team, modernizes the stack with async/await and a lightweight context object, leaving middleware choices entirely to the developer.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
express068,92675.4 kB2084 months agoMIT
fastify036,0282.78 MB13120 days agoMIT
hapi014,783-587 years agoBSD-3-Clause
koa035,71765 kB3716 days agoMIT

Express vs Fastify vs Hapi vs Koa: Architecture, Performance, and DX

When building a backend in Node.js, the framework you pick shapes how you handle requests, validate data, and structure your code. express, fastify, hapi, and koa are the main contenders. They all route HTTP traffic, but they solve it in very different ways. Let's look at how they handle the daily work of an API developer.

🏗️ Core Architecture: Middleware vs Configuration

express relies on a middleware chain. You add functions that run one after another. It is flexible but can get messy if you don't organize files well.

// express: Middleware chain
const express = require('express');
const app = express();

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

app.get('/user', (req, res) => {
  res.send('User Data');
});

fastify uses a plugin architecture and hooks. It is designed for speed and encapsulates logic to avoid global state pollution.

// fastify: Plugin and Hooks
const fastify = require('fastify')();

fastify.addHook('onRequest', async (request, reply) => {
  console.log('Incoming request');
});

fastify.get('/user', async (request, reply) => {
  return { hello: 'world' };
});

hapi avoids middleware entirely. Instead, you configure extensions and routes with detailed options. It forces a structured approach.

// hapi: Configuration and Extensions
const Hapi = require('@hapi/hapi');
const server = Hapi.server({ port: 3000 });

server.ext('onRequest', (request, h) => {
  console.log('Request started');
  return h.continue;
});

server.route({
  method: 'GET',
  path: '/user',
  handler: (request, h) => 'User Data'
});

koa uses a stack-like middleware system but with async/await. It gives you a clean ctx object instead of separate req and res.

// koa: Async Middleware Stack
const Koa = require('koa');
const app = new Koa();

app.use(async (ctx, next) => {
  console.log('Time:', Date.now());
  await next();
});

app.use(async (ctx) => {
  ctx.body = 'User Data';
});

📝 Validation and Serialization

express has no built-in validation. You must install libraries like joi, zod, or express-validator and wire them up manually.

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

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

fastify has validation built-in using JSON Schema. It also serializes responses quickly based on the schema.

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

hapi uses joi (often bundled or integrated) for rigorous validation rules defined in the route config.

// hapi: Joi validation in route options
const Joi = require('joi');

server.route({
  method: 'POST',
  path: '/user',
  options: {
    validate: {
      payload: Joi.object({ email: Joi.string().required() })
    }
  },
  handler: (request, h) => ({ id: 1, email: request.payload.email })
});

koa like Express, leaves validation to you. You pick your own library and middleware.

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

app.use(validator());

app.post('/user', async (ctx) => {
  ctx.assert(ctx.request.body.email, 400, 'Email required');
  ctx.body = { id: 1 };
});

🚨 Error Handling

express uses a special middleware function with four arguments. If you forget the next(err), the app hangs.

// express: Error middleware
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).send('Something broke!');
});

fastify lets you set a global error handler or handle errors inside routes using try/catch.

// fastify: Global error handler
fastify.setErrorHandler((error, request, reply) => {
  console.error(error);
  reply.status(500).send({ error: 'Internal Server Error' });
});

hapi handles errors via the response toolkit. You return an error object or throw one, and the framework formats it.

// hapi: Error handling in handler
server.route({
  method: 'GET',
  path: '/error',
  handler: (request, h) => {
    throw new Error('Something broke');
    // Or: return h.response().code(500);
  }
});

koa uses a central error handler middleware. Since it uses promises, unhandled rejections are caught here.

// koa: Central error handler
app.use(async (ctx, next) => {
  try {
    await next();
  } catch (err) {
    ctx.status = err.status || 500;
    ctx.body = err.message;
  }
});

🚀 Performance and Overhead

express is stable but older. It does not serialize JSON as fast as newer frameworks and has higher overhead per request.

// express: Standard JSON send
res.json({ message: 'Hello' }); 
// Uses JSON.stringify internally

fastify is built for speed. It uses fast-json-stringify to serialize responses up to 2x faster than standard JSON.

// fastify: Optimized serialization
reply.send({ message: 'Hello' }); 
// Uses schema-based serialization if defined

hapi prioritizes security and structure over raw speed. It is generally slower than Fastify but consistent.

// hapi: Standard response
return h.response({ message: 'Hello' });
// Robust but heavier processing

koa is lightweight and modern. It is faster than Express because it doesn't bundle legacy features, but it lacks Fastify's serialization tricks.

// koa: Body assignment
ctx.body = { message: 'Hello' };
// Standard JSON.stringify

🧩 Ecosystem and Plugins

express has the largest ecosystem. If a tool exists for Node, it likely has an Express middleware.

// express: Huge middleware variety
const cors = require('cors');
const helmet = require('helmet');
app.use(cors());
app.use(helmet());

fastify has a growing plugin ecosystem. Plugins are encapsulated, which prevents conflicts but requires learning the specific API.

// fastify: Encapsulated plugins
fastify.register(require('@fastify/cors'));
fastify.register(require('@fastify/helmet'));

hapi has a curated set of plugins. Quality is high, but variety is lower than Express.

// hapi: Official and community plugins
await server.register(require('@hapi/inert')); // Static assets
await server.register(require('@hapi/vision')); // Templates

koa shares much of the Express middleware ecosystem but requires wrappers for some older callback-based middleware.

// koa: Middleware compatibility
const cors = require('@koa/cors');
app.use(cors());
// Some express middleware needs 'koa-connect' wrapper

📊 Summary Table

Featureexpressfastifyhapikoa
StyleMiddlewarePlugin + SchemaConfigurationAsync Middleware
ValidationExternal (Manual)Built-in (JSON Schema)Built-in (Joi)External (Manual)
Error Handling4-arg MiddlewareHandler / Try-CatchToolkit / ThrowTry-Catch Middleware
PerformanceStandardHigh (Optimized)StandardLightweight
Learning CurveLowMediumHighMedium
Best ForGeneral PurposeHigh ThroughputEnterpriseModern Custom Stacks

💡 The Big Picture

express is the reliable workhorse 🐴. It is everywhere, easy to learn, and gets the job done for 90% of projects. Use it when you want to spend time on business logic, not framework configuration.

fastify is the speed demon 🏎️. If you are building microservices that need to handle thousands of requests per second, or if you love schema-driven development, this is the modern choice.

hapi is the enterprise vault 🏦. It is strict and secure by default. Choose it if you are in a regulated industry or need a framework that enforces good patterns across a large team.

koa is the minimalist's canvas 🎨. It gives you the modern async features of Express without the legacy weight. Pick it if you want to build your own architecture from the ground up.

Final Thought: All four frameworks can build production-ready APIs. The decision comes down to whether you value ecosystem size (express), raw performance (fastify), strict governance (hapi), or modern minimalism (koa).

How to Choose: express vs fastify vs hapi vs koa

  • express:

    Choose express if you need the widest possible selection of third-party middleware, tutorials, and community support. It is the safest bet for standard REST APIs, prototypes, and teams that value convention and ease of hiring. Stick with it if you don't have strict performance requirements that demand micro-optimization.

  • fastify:

    Choose fastify if performance is a top priority or if you want built-in validation and serialization without adding extra libraries. It is ideal for high-throughput microservices and teams that prefer schema-driven development to catch errors early in the request lifecycle.

  • hapi:

    Choose hapi if you are building large-scale enterprise applications where security, input validation, and strict configuration are more important than flexibility. It suits teams that want a 'batteries-included' framework with strong governance and less reliance on the quality of external middleware.

  • koa:

    Choose koa if you want a modern, lightweight foundation that uses async/await natively and gives you full control over middleware composition. It is best for developers who find Express callbacks cumbersome and want to build a custom stack without the baggage of legacy Express design decisions.

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