
Node.js: The fastest web framework in 2024
It’s been quite sometime since I’ve done a master comparison of Node.js web frameworks. It’s 2024, and an updated comparison is long overdue.
In this article, I’ll compare the performance of Node’s native server, Express, Fastify, Hapi, Koa, Restify, NestJS, NestJS (Fastify), Hyper Express, and AdonisJS for a simple hello world case. Of course, the starter hello world case is not ‘real-world’. But, it is always the starting point. Depending on the interest, I’ll follow up with more complex cases that include static file server, JSON processing, etc.
Note: The comparison of web frameworks for real-world static file server case is out now (you can read it here), and for real-world database reads case is also out now (you can read it here).
Before going further, if you want to look at more real-world cases, you can see my main article here.
Test setup
All tests are executed on MacBook Pro M2 with 16G RAM. There are 8+4 CPU cores. The load tester is Bombardier (written in Go). The Node.js version is v21.5.0 (latest at the time of writing).
I’m including Node’s native server too. This gives a good baseline for all the other frameworks. It is well known that Node’s native server is pretty much a bare-bones server.
The code for each application is as follows:
Native
import http from "node:http";
const reqHandler = (req, resp) => {
try {
if (req.method !== "GET") {
return resp.writeHead(405).end();
}
if (req.url !== "/") {
return resp.writeHead(404).end();
}
resp.writeHead(200, {
"content-type": "text/plain",
});
resp.end("Hello world");
} catch (e) {
resp.writeHead(500).end();
}
};
http.createServer(reqHandler).listen(3000);
Note: Native code is longer so that it is close to the other frameworks where we usually don’t see this code.
Express
(Etag is disabled because other frameworks doesn’t send it by default)
import express from "express";
const app = express();
const reqHandler = (req, res) => {
res.send("Hello World!");
};
app.disable("etag");
app.get("/", reqHandler);
app.listen(3000, () => console.log("Listening on 3000"));
Fastify
import Fastify from "fastify";
const fastify = Fastify({
logger: false,
});
const reqHandler = (request, reply) => {
reply.send("Hello World!");
};
fastify.get("/", reqHandler);
fastify.listen({ port: 3000 });
Koa
import Koa from "koa";
import Router from "@koa/router";
const app = new Koa();
const router = new Router();
const reqHandler = (ctx, next) => {
ctx.body = "Hello World!";
};
router.get("/", reqHandler);
app
.use(router.routes())
.use(router.allowedMethods());
app.listen(3000);
Hapi
import Hapi from "@hapi/hapi";
const server = Hapi.server({
port: 3000,
host: "127.0.0.1",
});
const reqHandler = (request, h) => {
return "Hello World!";
};
server.route({
method: "GET",
path: "/",
handler: reqHandler,
});
server.start();
Restify
import restify from "restify";
const reqHandler = (req, res, next) => {
res.contentType = "text/plain";
res.send("Hello World!");
next();
};
var server = restify.createServer();
server.get("/", reqHandler);
server.listen(3000);
Hyper Express
import HyperExpress from "hyper-express";
const webserver = new HyperExpress.Server();
const reqHandler = (request, response) => {
response.send("Hello World!");
};
webserver.get("/", reqHandler);
webserver.listen(3000);
NestJS
(The NestJS app code is generated through their CLI. Only few relevant files are shown here)
main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.getHttpAdapter().getInstance().set('etag', false);
await app.listen(3000);
}
bootstrap();
app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
app.controller.ts
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
}
app.service.ts
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getHello(): string {
return 'Hello World!';
}
}
NestJS (Fastify)
By default, NestJS uses express adapter. Therefore, the performance is slightly worse than Express (which itself is the slowest in this lot). Fortunately, NestJS allows configuring fastify adapter instead of express.
main.ts
import { NestFactory } from "@nestjs/core";
import {
FastifyAdapter,
NestFastifyApplication,
} from "@nestjs/platform-fastify";
import { AppModule } from "./app.module";
async function bootstrap() {
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter(),
);
await app.listen(3000);
}
bootstrap();
All other files are same as NestJS (express).
AdonisJS
(The AdonisJS app code is generated through their CLI. Only few relevant files are shown here)
server.ts
import 'reflect-metadata'
import sourceMapSupport from 'source-map-support'
import { Ignitor } from '@adonisjs/core/build/standalone'
sourceMapSupport.install({ handleUncaughtExceptions: false })
new Ignitor(__dirname)
.httpServer()
.start()
routes.ts
import Route from '@ioc:Adonis/Core/Route'
Route.get('/', async () => {
return "Hello World!"
})
Results
A total of 5M requests are executed for 100 concurrent connections. Along with latency, I’ve also collected CPU and memory usage. This gives a complete picture. Just having high RPS is not enough. We also need to know the cost of the RPS.
The results in chart form are as follows:
Time taken & RPS


Latencies






Resource usage


Verdict
Before we start the analysis, we should note down that this is quite a mix of feature rich and regular frameworks. Out of this lot, NestJS & AdonisJS are opinionated frameworks with significantly more functionality compared to all others. It is not fair to compare NestJS with Koa (for example).
Quite surprisingly, HyperExpress turns out to be the star of this show. HyperExpress is about two times faster than Fastify, nine times faster than Express, and about three to four times faster than the others. HyperExpress deliver exceptional performance with lesser resource cost.
I never knew about HyperExpress (Thanks to the reader who has suggested it). I always considered Fastify when I expected near native performance.
I’ll be subjecting HyperExpress to more real-world scenarios. It’d be interesting to see how it compares to other notable frameworks such as Express, Fastify, and Koa for cases like HTTPS, JSON handling, DB interactions. The winning margin for real-world cases may not be as wide as the simplest ‘hello-world’ case. Definitely, an interesting case to look at.
WINNER: HyperExpress
This was only about the do-nothing ‘hello world’ server. The winning margins might not be as wide for more complex cases. I’ll get to them very soon.
Note: The comparison of web frameworks for real-world static file server case is out now (you can read it here), and for real-world database reads case is also out now (you can read it here).
Thanks for reading this article!