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.
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.
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 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']
}
};
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
}
}
};
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.
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.
| Feature | express | fastify | hapi | koa | sails |
|---|---|---|---|---|---|
| Philosophy | Minimal, Unopinionated | High Performance, Schema-based | Configuration, Enterprise | Minimal, Async/Await | MVC, Convention over Config |
| Routing | Code-based | Code-based | Config-based | External Module | Auto-generated / Config |
| Validation | External Middleware | Built-in (JSON Schema) | Built-in (Joi) | External Middleware | Model-based |
| Async Model | Callbacks / Promises | Promises / Async | Promises / Async | Async / Await | Async / Await |
| Learning Curve | Low | Low/Medium | Medium | Medium | High |
| Best For | General Purpose, Legacy | Microservices, High Load | Enterprise, Complex Apps | Custom Architectures | Rapid CRUD Prototyping |
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.
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.
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.
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.
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.
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.
An efficient server implies a lower cost of the infrastructure, better responsiveness under load, and happy users. How can you efficiently handle the resources of your server, knowing that you are serving the highest number of requests possible, without sacrificing security validations and handy development?
Enter Fastify. Fastify is a web framework highly focused on providing the best developer experience with the least overhead and a powerful plugin architecture. It is inspired by Hapi and Express and as far as we know, it is one of the fastest web frameworks in town.
The main branch refers to the Fastify v5 release.
Check out the 4.x branch for v4.
Create a folder and make it your current working directory:
mkdir my-app
cd my-app
Generate a fastify project with npm init:
npm init fastify
Install dependencies:
npm i
To start the app in dev mode:
npm run dev
For production mode:
npm start
Under the hood npm init downloads and runs Fastify
Create, which in turn uses the
generate functionality of Fastify CLI.
To install Fastify in an existing project as a dependency:
npm i fastify
// Require the framework and instantiate it
// ESM
import Fastify from 'fastify'
const fastify = Fastify({
logger: true
})
// CommonJs
const fastify = require('fastify')({
logger: true
})
// Declare a route
fastify.get('/', (request, reply) => {
reply.send({ hello: 'world' })
})
// Run the server!
fastify.listen({ port: 3000 }, (err, address) => {
if (err) throw err
// Server is now listening on ${address}
})
With async-await:
// ESM
import Fastify from 'fastify'
const fastify = Fastify({
logger: true
})
// CommonJs
const fastify = require('fastify')({
logger: true
})
fastify.get('/', async (request, reply) => {
reply.type('application/json').code(200)
return { hello: 'world' }
})
fastify.listen({ port: 3000 }, (err, address) => {
if (err) throw err
// Server is now listening on ${address}
})
Do you want to know more? Head to the Getting Started.
If you learn best by reading code, explore the official demo.
Note
.listenbinds to the local host,localhost, interface by default (127.0.0.1or::1, depending on the operating system configuration). If you are running Fastify in a container (Docker, GCP, etc.), you may need to bind to0.0.0.0. Be careful when listening on all interfaces; it comes with inherent security risks. See the documentation for more information.
Machine: EX41S-SSD, Intel Core i7, 4Ghz, 64GB RAM, 4C/8T, SSD.
Method: autocannon -c 100 -d 40 -p 10 localhost:3000 * 2, taking the
second average
| Framework | Version | Router? | Requests/sec |
|---|---|---|---|
| Express | 4.17.3 | β | 14,200 |
| hapi | 20.2.1 | β | 42,284 |
| Restify | 8.6.1 | β | 50,363 |
| Koa | 2.13.0 | β | 54,272 |
| Fastify | 4.0.0 | β | 77,193 |
| - | |||
http.Server | 16.14.2 | β | 74,513 |
These benchmarks taken using https://github.com/fastify/benchmarks. This is a synthetic "hello world" benchmark that aims to evaluate the framework overhead. The overhead that each framework has on your application depends on your application. You should always benchmark if performance matters to you.
Getting StartedGuidesServerRoutesEncapsulationLoggingMiddlewareHooksDecoratorsValidation and SerializationFluent SchemaLifecycleReplyRequestErrorsContent Type ParserPluginsTestingBenchmarkingHow to write a good pluginPlugins GuideHTTP2Long Term SupportTypeScript and types supportServerlessRecommendationsPlease visit Fastify help to view prior support issues and to ask new support questions.
Version 3 of Fastify and lower are EOL and will not receive any security or bug fixes.
Fastify's partner, HeroDevs, provides commercial security fixes for all unsupported versions at https://herodevs.com/support/fastify-nes. Fastify's supported version matrix is available in the Long Term Support documentation.
Whether reporting bugs, discussing improvements and new ideas, or writing code, we welcome contributions from anyone and everyone. Please read the CONTRIBUTING guidelines before submitting pull requests.
Fastify is the result of the work of a great community. Team members are listed in alphabetical order.
Lead Maintainers:
Great contributors to a specific area of the Fastify ecosystem will be invited to join this group by Lead Maintainers when they decide to step down from the active contributor's group.
We are an At-Large Project in the OpenJS Foundation.
Support this project by becoming a SPONSOR! Fastify has an Open Collective page where we accept and manage financial contributions.
This project is kindly sponsored by:
Past Sponsors:
This list includes all companies that support one or more team members in maintaining this project.
Licensed under MIT.
For your convenience, here is a list of all the licenses of our production dependencies: