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.
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.
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/hellowhich returns HTTP 200, useful for establishing a baseline.
/api/restthat returns a predefined object as JSON
/api/trpcthat returns the same object but through trpc
/api/graphqlthat 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).
wrk -t1 -c1 -d5s "http://localhost:3000/api/hello”
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.
wrk -t1 -c1 -d5s -H "Content-Type: application/json" "http://localhost:3000/api/rest"
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.
wrk -t1 -c1 -d5s -H "Content-Type: application/json" "http://localhost:3000/api/trpcTest?batch=1&input=%7B%7D"
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.
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?
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.
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.