DEV Community

Костя Третяк
Костя Третяк

Posted on

A quick overview of Bun's basic features and how it compares to Node.js

Bun is a JavaScript runtime, positioned by its author as a set of ready-to-use tools "out of the box" provided along with the JavaScript runtime: this includes a package manager, a bundler, and a test runner. Additionally, the author strives to offer strong TypeScript support so that there’s no need to compile TypeScript files into JavaScript files. It's been almost a year since the first version of Bun was released. In this short post, I will examine how well the claimed features align with reality and the performance it delivers.

Installing Bun

As shown on Bun's main page, on Linux or macOS, it can be installed as follows:

curl -fsSL https://bun.sh/install | bash 
Enter fullscreen mode Exit fullscreen mode

It can be upgraded to the next stable version with:

bun upgrade --stable 
Enter fullscreen mode Exit fullscreen mode

Cloning the Repository with Benchmark Code

In this article, we will use the code that can be found in my repository. Clone it, navigate to the new directory, and install dependencies:

git clone https://github.com/KostyaTretyak/bun-vs-node.git cd bun-vs-node bun install 
Enter fullscreen mode Exit fullscreen mode

Additionally, install the wrk benchmarking utility on your computer.

Comparing the Speed of Bun's Native Web Server and Node.js

Node.js on single process

First, let's see what Node.js shows, and then compare these results with Bun benchmarks. So, in the first terminal, run the node.mjs file:

node node.mjs 
Enter fullscreen mode Exit fullscreen mode

On my machine, the cold start takes about 5ms.

Now, in the second terminal, run the benchmark:

wrk -H 'Connection: keep-alive' --latency -d 5 -c 256 --timeout 8 -t 4 http://localhost:3000/plaintext 
Enter fullscreen mode Exit fullscreen mode

This command makes requests in four threads, with a concurrency of 256, for a duration of 5 seconds. On my machine, the average result is as follows:

Running 5s test @ http://localhost:3000/plaintext 4 threads and 256 connections Thread Stats Avg Stdev Max +/- Stdev Latency 73.32ms 222.54ms 1.83s 92.72% Req/Sec 4.50k 665.94 6.39k 76.50% Latency Distribution 50% 13.74ms 75% 14.93ms 90% 79.47ms 99% 1.23s 89513 requests in 5.02s, 16.39MB read Requests/sec: 17846.79 Transfer/sec: 3.27MB 
Enter fullscreen mode Exit fullscreen mode

So, about 17 thousand requests per second.

Node.js with cluster mode

Now let's try using the cluster mode. To do this, go back to the first terminal, stop the process by pressing Ctrl+C, and run the following:

node node-cluster.mjs 
Enter fullscreen mode Exit fullscreen mode

On my machine, it has 4 cores, and each process in the cluster starts twice as slowly - about 10ms on average.

Now, run the benchmark again:

wrk -H 'Connection: keep-alive' --latency -d 5 -c 256 --timeout 8 -t 4 http://localhost:3000/plaintext 
Enter fullscreen mode Exit fullscreen mode

This time, the results are as follows:

Running 5s test @ http://localhost:3000/plaintext 4 threads and 256 connections Thread Stats Avg Stdev Max +/- Stdev Latency 9.03ms 18.69ms 345.62ms 97.60% Req/Sec 9.26k 1.55k 12.23k 83.50% Latency Distribution 50% 6.42ms 75% 8.32ms 90% 10.85ms 99% 85.68ms 184883 requests in 5.03s, 33.85MB read Requests/sec: 36720.73 Transfer/sec: 6.72MB 
Enter fullscreen mode Exit fullscreen mode

So, 36 thousand requests per second, which is slightly more than twice as fast compared to running on a single core without cluster mode.

Bun on single process

Now let's run the same benchmarks with Bun. Go to the first terminal, press Ctrl+C, and run:

bun bun.mjs 
Enter fullscreen mode Exit fullscreen mode

The process starts in about 4ms (almost as fast as in Node.js). When running the same benchmark with wrk, the following result is obtained:

Running 5s test @ http://localhost:3000/plaintext 4 threads and 256 connections Thread Stats Avg Stdev Max +/- Stdev Latency 5.42ms 769.54us 26.37ms 93.00% Req/Sec 11.87k 0.86k 12.95k 75.00% Latency Distribution 50% 5.28ms 75% 5.80ms 90% 6.04ms 99% 6.77ms 236197 requests in 5.04s, 31.99MB read Requests/sec: 46849.13 Transfer/sec: 6.34MB 
Enter fullscreen mode Exit fullscreen mode

So, Bun handles 46 thousand requests per second on a single core, which is 2.5 times faster compared to Node.js.

Bun with cluster mode

Again, go to the first terminal, press Ctrl+C, and run:

bun bun-cluster.mjs 
Enter fullscreen mode Exit fullscreen mode

We see that in cluster mode, Bun's processes start just as quickly as without cluster mode.

Now, run the benchmark with wrk again, and the following result is obtained:

Running 5s test @ http://localhost:3000/plaintext 4 threads and 256 connections Thread Stats Avg Stdev Max +/- Stdev Latency 3.09ms 2.08ms 32.22ms 70.02% Req/Sec 19.97k 1.99k 27.94k 85.50% Latency Distribution 50% 2.86ms 75% 4.16ms 90% 5.70ms 99% 9.41ms 399353 requests in 5.07s, 54.08MB read Requests/sec: 78753.89 Transfer/sec: 10.66MB 
Enter fullscreen mode Exit fullscreen mode

So, 78 thousand requests per second. There is a performance increase, but only about 1.5 times compared to non-cluster mode. However, this is twice as fast as Node.js in cluster mode.

Describing a summary from my benchmarking experience, I can say that these are the best results comparing "Bun vs Node.js". It seems that Bun's web server is twice (or sometimes four times) faster, but this speed does not extend to all code. As you gradually increase the amount of JavaScript code in your application, Bun's performance will quickly degrade.

And when Bun works with database code, it shows noticeably worse results compared to Node.js. As far as I understand, this happens due to the significant amount of JavaScript code in database drivers.

How Bun Works with ExpressJS

Run it with the following command:

bun express.mjs 
Enter fullscreen mode Exit fullscreen mode

On my machine, Bun launches this code in about 10ms, while Node.js launches it in 4ms, meaning Node.js has a 2.5 times faster cold start.

However, Bun has a special feature designed to minimize startup time. To use it, create an executable file using the following command:

bun build ./express.mjs --compile --minify --outfile mycli 
Enter fullscreen mode Exit fullscreen mode

It will generate a mycli file from express.mjs, which can be run as follows:

./mycli 
Enter fullscreen mode Exit fullscreen mode

On my machine, this file launches slightly slower (about one millisecond slower) than the uncompiled file. However, this feature is primarily intended for large projects, so no optimization for cold start is noticeable in minimal code. However, the speed of the compiled file is slightly higher.

How Bun Works with Ditsmod

If you clone the seeds from Ditsmod and install the dependencies:

git clone --depth 1 https://github.com/ditsmod/seed.git my-app cd my-app bun install 
Enter fullscreen mode Exit fullscreen mode

And try to immediately run the application using Bun:

bun run build bun dist/main.js 
Enter fullscreen mode Exit fullscreen mode

It will throw the following error:

1 | (function (entry, fetcher) ^ SyntaxError: export 'ValueProvider' not found in './types-and-models.js' 
Enter fullscreen mode Exit fullscreen mode

This bug is easily fixed - just delete all tsconfig files from node_modules/@ditsmod:

rm node_modules/@ditsmod/*/tsconfig.json 
Enter fullscreen mode Exit fullscreen mode

That's it, now Bun works as expected:

bun dist/main.js 
Enter fullscreen mode Exit fullscreen mode

Summary

Personally, I don't yet see any significant advantages for switching to Bun. It has fast dependency installation, a fast web server, and possibly fast SQLite. If you open its repository, you can see that the number of bugs is overwhelming, so the Bun team has a hard time handling them.

If nothing significant happens in Bun's development over the next year, it will be unpromising for me.

Top comments (0)