express, fastify, koa, and nestjs are all tools for building server-side applications in JavaScript and TypeScript. express is the classic choice with a huge ecosystem and minimal setup. fastify focuses on speed and low overhead with built-in validation. koa is a modern, lightweight core by the Express team that uses async functions. nestjs is a full-featured framework using decorators and dependency injection for large-scale architecture.
These four tools solve the same problem — building servers in Node.js — but they take very different approaches to structure, performance, and developer experience. Understanding their core differences helps you pick the right foundation for your project.
express is minimal and unopinionated.
// express: Minimal setup
const express = require('express');
const app = express();
app.use(express.json());
app.listen(3000);
fastify is minimal but structured for speed.
// fastify: Plugin-based setup
const fastify = require('fastify')();
fastify.register(require('./routes/user'));
fastify.listen({ port: 3000 });
koa is even more barebones than Express.
// koa: Composed middleware
const Koa = require('koa');
const app = new Koa();
app.use(async (ctx, next) => { await next(); });
app.listen(3000);
nestjs is a full-stack framework with strict rules.
// nestjs: Module-based setup
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
}
bootstrap();
express uses function-based routes.
req and res objects.// express: Function route
app.get('/users', (req, res) => {
res.json({ users: [] });
});
fastify uses function-based routes with schemas.
request and reply objects.// fastify: Schema route
fastify.get('/users', async (request, reply) => {
return { users: [] };
});
koa uses middleware for routing.
@koa/router).ctx object.// koa: Middleware route
const Router = require('@koa/router');
const router = new Router();
router.get('/users', (ctx) => {
ctx.body = { users: [] };
});
nestjs uses decorators for routes.
// nestjs: Decorator route
@Controller('users')
export class UserController {
@Get()
findAll() {
return { users: [] };
}
}
express relies on callback-style middleware.
next() to continue to the next function.// express: Middleware
app.use((req, res, next) => {
console.log('Time:', Date.now());
next();
});
fastify uses hooks and plugins.
preHandler hooks run before route logic.// fastify: Hook
fastify.addHook('preHandler', async (request, reply) => {
console.log('Time:', Date.now());
});
koa uses async middleware composition.
await next() pauses execution until downstream finishes.// koa: Async Middleware
app.use(async (ctx, next) => {
console.log('Time:', Date.now());
await next();
});
nestjs uses guards and interceptors.
CanActivate.// nestjs: Guard
@Injectable()
export class TimeGuard implements CanActivate {
canActivate() {
console.log('Time:', Date.now());
return true;
}
}
express requires external libraries.
Joi, Zod, or express-validator.// express: External validation
app.post('/users', validateUserSchema, (req, res) => {
res.send('Created');
});
fastify has validation built-in.
// fastify: Built-in validation
fastify.post('/users', {
schema: { body: { type: 'object' } }
}, async (req, reply) => {
return 'Created';
});
koa requires external libraries.
koa-bodyparser.// koa: External validation
router.post('/users', validateBody, async (ctx) => {
ctx.body = 'Created';
});
nestjs has validation built-in via decorators.
class-validator decorators on DTO classes.// nestjs: Decorator validation
export class CreateUserDto {
@IsString()
name: string;
}
express needs community types.
@types/express separately.// express: Type extension
interface UserRequest extends Request {
user?: { id: string };
}
fastify has strong types built-in.
// fastify: Generic types
fastify.get<{ Params: { id: string } }>('/users/:id', async (req) => {
return req.params.id;
});
koa needs community types.
@types/koa.// koa: Context state
interface ContextState {
user?: { id: string };
}
nestjs is built for TypeScript.
// nestjs: Native types
@Controller('users')
export class UserController {
@Get(':id')
findOne(@Param('id') id: string): Promise<User> {
return this.service.findOne(id);
}
}
| Feature | express | fastify | koa | nestjs |
|---|---|---|---|---|
| Style | Minimal | Minimal + Speed | Minimal + Modern | Full-Stack |
| Routing | Functions | Functions | Middleware | Decorators |
| Validation | External | Built-in | External | Built-in |
| TypeScript | Community Types | Strong Generics | Community Types | Native Support |
| Learning Curve | Low | Low | Medium | High |
express is the reliable workhorse 🐴 — it gets the job done with minimal fuss and maximum community support. Use it for standard APIs or when you need a specific plugin that only exists for Express.
fastify is the performance specialist 🏎️ — it gives you speed and structure without forcing a heavy framework on you. Use it for microservices or high-throughput systems where validation overhead matters.
koa is the modern minimalist 🧘 — it cleans up the async flow of Express but leaves architecture up to you. Use it if you want full control over middleware composition without legacy baggage.
nestjs is the enterprise architect 🏢 — it enforces structure and testability for large codebases. Use it when team consistency and long-term maintainability outweigh initial setup complexity.
Final Thought: All four tools are actively maintained and capable. The choice depends on how much structure you want upfront — express and koa let you build it yourself, while fastify and nestjs provide more guardrails out of the box.
Choose express if you need a stable, widely supported server with endless plugins and tutorials. It is best for teams that want convention-free setup and vast community resources for troubleshooting. Ideal for legacy upgrades or simple APIs where raw speed is not the main bottleneck.
Choose fastify if performance and low resource usage are critical for your service. It offers built-in validation and logging without extra setup, reducing boilerplate code. Great for high-traffic microservices or when you want speed without sacrificing structure.
Choose koa if you want a modern async core without the baggage of older callback-based middleware. It gives you full control over the request context without forcing a specific file structure. Best for developers who prefer building their own stack from lightweight, composable parts.
Choose nestjs if you need a structured, Angular-like architecture for large teams working on complex systems. It includes dependency injection and modules out of the box, enforcing consistency across the codebase. Perfect for enterprise apps where testability and long-term maintenance matter most.
Fast, unopinionated, minimalist web framework for Node.js.
This project has a Code of Conduct.
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')
})
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.
PROTIP Be sure to read the migration guide to v5
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
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.
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
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.
If you discover a security vulnerability in Express, please see Security Policies and Procedures.
To run the test suite, first install the dependencies:
npm install
Then run npm test:
npm test
For information about the governance of the express.js project, see GOVERNANCE.md.
The original author of Express is TJ Holowaychuk