Can Node.js run Hello World as fast as Bun?

Mayank C
Tech Tonic

--

The claim to fame for Bun is ‘speed’. Of course, there is a parallel track on developer experience, however it finally boils down to speed. Why would someone use Bun over the battle-tested & immensely popular Node.js? To start with, Bun can be a [fast] drop-in replacement for Node.js apps. This means that, you can run the same app in Bun and achieve more throughput. I’ve found it to be true for some cases, but not for all. You can my real-world comparison article here.

One of the basic work for runtimes like Node.js, Deno, and Bun is serving HTTP. This is their bread and butter. Bun’s official numbers claim it to be 4–5 times faster than Node.js. This is true for the basic case like ‘hello world’, but not true at all for real-world cases. At least the winning margin is not as high as 4 times.

Coming back to the basic ‘Hello World’ case: Why is Bun’s HTTP server so fast? The Bun’s code is highly optimized, runs on Zig, uses JSC instead of V8, JSC is fast, etc. All the reasons are valid. However, the correct answer is uWebSockets. This is the underlying library that powers Bun’s HTTP server (See Bun’s code here).

The same blazing fast uWebSocket library is also available for Node.js. So, what happens if you run a ‘hello world’ HTTP server in Node.js using uWebSockets and compare that with Bun’s built-in HTTP server.

Before we do that, we should note down one important aspect: Bun is offering a web standard interface (Request, Response, Blob, etc.) over uWebSockets. The Node.js app using uWebSockets is not going to do any of that. Therefore, the comparison may not be as fair as wanted. But, we’ll go ahead and try it out.

Test setup

As always, the tests are executed on MacBook M2 with 16G RAM and 8+4 cores. The load tester is Bombardier, written in Go. The versions are:

  • Bun 1.0.27
  • Node.js v21.6.2

The application code is as follows:

Node.js

import { App } from "uWebSockets.js";

const app = App();

function reqHandler(res, req) {
res.end("Hello World!");
}

app.get("/", reqHandler);
app.listen(3000, () => console.log("Server listening on 3000"));

Bun

As Bun’s built-in server doesn’t provide a router, I’ve written a minimal one to make it a fair comparison.

Bun.serve({
port: 3000,
fetch(req) {
try {
const pathName = new URL(req.url).pathname;
if (pathName !== "/") {
return new Response(null, { status: 404 });
}
return new Response("Hello world");
} catch (e) {
return new Response(null, { status: 500 });
}
},
});

Results

Each test of 50, 100, 300 concurrent connections is executed for 5M requests. The results in chart form are as follows:

Verdict

Quite surprisingly, the simple ‘hello world’ Node.js app powered with uWebSockets outperforms Bun’s built-in server by a small margin. The small margin can be attributed to the additional work Bun is doing (maintaining web standard interfaces).

The latencies & resource usage is comparable. Not much difference there.

Winner: Node.js (Or, uWebSockets)

Thanks for reading this article.

--

--