Spring Boot Webflux vs Node.js frameworks: Performance comparison for hello world case

Mayank C
Tech Tonic

--

This is a requested article.

This article compares Spring Boot Webflux 3.2 with all popular frameworks on the Node.js side.

Note: Some of the Node frameworks are very basic, while some are feature rich. We’ll still compare with all of them.

Test setup

All tests are executed on MacBook Pro M2 with 16G RAM & 8+4 CPU cores. The load tester is Bombardier (Written in Go). The software versions are:

  • Java 21
  • Spring Boot Webflux 3.2.1
  • Node.js v21.5.0

The application code is as follows:

Java

application.properties

server.port=3000
spring.threads.virtual.enabled=true

Application.java

package hello;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.web.reactive.config.EnableWebFlux;
import org.reactivestreams.Publisher;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import reactor.core.publisher.Mono;

@SpringBootApplication
@EnableWebFlux
@EnableAsync
@Controller
public class Application {

public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class);
}

@GetMapping("/")
@ResponseBody
public Publisher<String> handler() {
return Mono.just("Hello world!");
}
}

Node.js

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);

HyperExpress

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

Impressions

Spring Boot Webflux is an extensive, feature-rich framework that offers numerous things out of the box. Comparatively, on the Node side, the closest competitor is NestJS. Others like Express, Fastify, Restify, Hapi, etc. are way too small compared to Webflux.

From the performance point of view, Spring Webflux gets beaten only by one framework: HyperExpress. On the resource front, Webflux’s resource usage is very high compared to Node frameworks.

Thanks for reading this article!

--

--