Bun vs Node.js performance - Next.js benchmarks

Let's compare performance of Bun and Node.js head-to-head, by benchmarking basic Next.js app.
Published 2023-09-25 6 min read
Bun vs Node.js performance - Next.js benchmarks

Today we are going to stack up Bun against Node.js with some Next.js benchmarks.

With all the hype around Bun, it’s time to check its real-life performance against Node.js. Benchmarks from the Bun team were promising - as Bun was 5x faster than a good-ol’ Node in most cases. Enough to say that benchmarks presented on the Bun website were hand-crafted to feature the best possible scenario.

Benchmarks from bun.sh website

In the real world, it’s much harder to see this kind of performance boost. That’s why I decided to check if swapping your runtime from Node.js to Bun makes sense from a performance perspective. Since Next.js is a popular choice for modern web development, the application we are going to benchmark is in this framework.

 

The setup

First of we are running Node 18.18.0 vs Bun 1.0.3.

The application is a really basic Node app, that exposes a few API endpoints. I’ve already used it to compare REST, GraphQL and trpc performance. The repository can be found on GitHub. I’ve updated dependencies and added next-bundle-analyzer (for another blog post on Next.js bundle size optimization).

For Node.js I build the application with yarn build and then I use yarn start to serve it.

For Bun I build the application using bun –bun run build and then bun –bun run start to serve it.

I’ve benchmarked 4 endpoints.

  • /api/hello which returns HTTP 200, useful for establishing a baseline.
  • /api/rest that returns a predefined object as JSON
  • /api/trpc that returns the same object but through trpc
  • /api/graphql that uses Apollo to return the same results

 

For each benchmark concurrency is set to 1, as I’m not concerned with overwhelming the application, but establishing how fast the application can respond in series.

 

Bun vs Node.js - benchmarks

The first idea was to use Hey, the same as in the previous comparison between API styles. This time things become much more complicated.

Hey worked only for api/hello, as it had problems with other responses returned from the server when running Bun. Never seen that before.

This forced me to switch to wrk, as ab tends to freeze on my machine (even with keep-alive option).

Baseline

wrk -t1 -c1 -d5s "http://localhost:3000/api/hello”

Node vs Bun performance - Next.js API that returns Head 200

As we can see from the plot, Node, and Bun performed similarly, with no real-world difference between the two. Both fetch over 12 000 rps.

REST

wrk -t1 -c1 -d5s -H "Content-Type: application/json" "http://localhost:3000/api/rest"

Node.js vs Bun - REST performance

This was interesting as Bun performed worse at first. You may ask why.

Because it logs a warning in the server console, making the requests slower. The results shown on the chart are after I redirected stdout to /dev/null. It was the biggest difference between Node.js and Bun in the whole suite. Around 8400 rps for Node, and around 10600 rps for Bun. 

trpc

wrk -t1 -c1 -d5s -H "Content-Type: application/json" "http://localhost:3000/api/trpcTest?batch=1&input=%7B%7D"

Node.js vs Bun - trpc benchmarks

Another close showing, with 8600 rps for Node.js and 9200 rps for Bun. It’s interesting that we somehow managed more rps than REST, which is a very basic endpoint.

GraphQL

wrk -s graphql-script.lua -t1 -c1 -d5s "http://localhost:3000/api/graphql"

lua script contains the request body and sets other required headers.

At first, I thought that this time Bun won decisively, as Node.js managed to get 5100 rps and the number for Bun came at over 8000 rps. But let us take a closer look at the output:

Running 5s test @ http://localhost:3000/api/graphql
    1 threads and 1 connections
    Thread Stats Avg Stdev Max +/- Stdev
    Latency 222.16us 436.26us 5.08ms 92.07%
    Req/Sec 8.89k 457.79 9.52k 86.27%
    45095 requests in 5.10s, 8.26MB read
    Non-2xx or 3xx responses: 45095
Requests/sec: 8841.34
Transfer/sec: 1.62MB

Yup, all requests failed, and the endpoint returns Failed to construct 'Request': Invalid URL "/api/graphql"%.

Bun vs Node.js - which is faster?

Benchmark results suggest that Bun is faster than Node.js by around 10% in a simple, API-based Next.js application. Not so glorious as 5x suggested by Bun’s website? Huh

Of course, Next.js is not optimized for Bun, but it’s much closer to the real world than Bun.serve() examples provided on their website.

Is Bun worth it?

Not really.

Bun is advertised as a blazing-fast drop-in replacement for Node or Deno. It is faster than Node.js, but not by much in the modern JS ecosystem, which was not created for Bun.

Now regarding the drop-in replacement part - this was the more disappointing one. Initially, I thought that all experiments would take about 10 minutes. Unfortunately, I encountered weird warnings, formatting that is incompatible with the Go HTTP library (that’s why I could use Hey), and a straight-up broken Apollo Server on Bun.

The application I tested is as simple as it gets, much simpler than 90% of JavaScript applications out in the wild. Yet, the problems I got are really hard for me to understand and I truly have no idea how to solve them. It seems I’m not alone, as Bun repo has over 1700 open issuesat the moment of writing.

Therefore the critique of Bun that appeared in the interwebs recently is probably justified. There are some good parts, but it’s far from being ready for wider adoption.

#nodejs #bun #performance