Bun: A successful revolution or a run-of-the-mill uprising?

Bun: A successful
revolution or a
run-of-the-mill
uprising?

Bun

So, is Bun a full-fledged substitute for Node.js or is it just another JavaScript fling?

These days, Node.js is one of the most popular technologies for web development and, in fact, a leading environment for executing JavaScript on a server. As technology evolves, new trends appear in web development and in light of the criticism of Node.js, even by its creator Ryan Dahl, new alternative server-side JavaScript runtime environments like Deno or Bun are emerging.

The Bun runtime environment, due to its unique features and advantages, is positioned by its creators not merely as a worthy but, at times, an indisputable alternative to Node.js. The frequency with which Bun is mentioned in most tech-related resources hints at a revolution unfolding in the world of server-side JavaScript. But, is this truly the case or are we witnessing a minor local uprising? In this article, I will strive to answer these questions.

What is a JavaScript runtime environment?

For a long time, the only place JavaScript could be used was in web browsers. In 2009, Ryan Dahl introduced a new technology to the world called Node.js.

Node.js was built around the V8 engine, the same one used in the Google Chrome browser, along with a set of libraries and tools to enable asynchronous I/O architecture and interaction with the operating system [server]. The combination of these components formed a runtime environment.

As time passed, both the strengths and weaknesses of the platform became evident, which Ryan Dahl himself highlighted in his well-known presentation, 10 Things I Regret about Node.js.

What’s Bun’s role in all of this?

While the creator of Node.js is actively promoting his new platform, Deno, Jarred Sumner spotted a bunch of issues with Node.js and decided to develop a new runtime environment that could revolutionize server-side JavaScript (JS) development: Bun.

As an alternative server-side option, Bun JavaScript runtime has been around for over a year. Over this period, the developer community and enthusiasts have rigorously tested this environment, provided their feedback, and engaged directly in the development process. In September 2023, the world saw the official release of Javascript Bun 1.0, which is production-ready according to its creators. Bun has often been presented as a drop-in replacement for Node.js, implying that you can transition from Node.js to Bun with just a few clicks and commands. In addition, Bun offers several advantages over Node.js, including:

  • Built-in TypeScript support
  • Built-in bundler
  • Built-in test runner
  • Native Node.js API support
  • Improved performance

How does it operate?

Bun runtime was created with a dual purpose in mind: on one hand, to simplify development on Node.js, and on the other hand, to enhance performance whenever possible. This was achieved by writing Bun in the low-level Zig programming language, which enables a high level of optimization. Additionally, Bun utilizes the JavaScriptCore Engine instead of V8, the engine used in the Safari browser, which delivers superior performance compared to V8.

Since Node.js didn’t provide a transpiler, a bundler, support for Typescript, and other essential features out-of-the-box, it resulted in the necessity of installing and maintaining a lot of third-party libraries and tools just to run or build a project. Bun solves these problems by offering many of these things right out-of-the-box.

In the image below, you’ll find a side-to-side comparison of Node.js and Bun:Node.js vs. Bun.jsFigure 1. Node.js vs. Bun.js

For example, Bun allows you to run files with .js, .jsx, .ts, and .tsx extensions immediately after installation due to its built-in transpiler. No extra libraries or tools need to be installed — it just works.

The same applies to support for the CommonJS and ES modules. Files with .cjs and .mjs extensions are supported right after Bun is installed. However, if you haven’t used them in your project, ‘require’ and ‘import’ will function without any additional settings.

This is just a small part of what Bun programming language streamlines and offers out-of-the-box, and I encourage you to visit its main website for more details.

Is Bun.js really that good?

As I mentioned above, the developers positioned Bun.js as a replacement for Node.js that doesn’t require any additional configuration or customization. Just install it and you’re all set.

I decided to check whether everything is really that good on a small production-ready project on GitHub that uses Node.js, Express, and Mongo. Having forked this repository, I immediately installed Bun as described in the official documentation:

curl -fsSL <https://bun.sh/install> | bash

Next, in the corresponding folder with the project, I deleted node_modules and ran the command:

bun install

As a result, Bun installed all the necessary dependencies and generated its binary file, bun.lock, which is somewhat akin to yarn.lock or package.lock. The speed at which Bun installs all the dependencies is also worth noting. Indeed, this process is much faster than when working with npm. Such speed is a result of numerous optimizations and approaches to handling dependencies and caching. However, as a consequence, other developers may encounter various difficulties and unexpected bugs.

Right before running it, I corrected the command responsible for launching it in development mode:

"dev": "cross-env NODE_ENV=development bun src/index.js",

Then, I launched the application with this command:

bun start

Unfortunately, the expected start did not occur. The console displayed the following error:

error: Cannot find package "mongodb-extjson" from "/Users/…./node_modules/mongodb/lib/core/utils.js”

Not only did I encounter this error, but other developers also faced it here. As a result, Bun didn’t work not only with my current project, but also with projects written using the Nest.js framework. There are also problems with pm2+ Bun.

In many cases, these and other issues are caused by an incomplete compatibility with the Node.js Native API. You can review the corresponding statuses in the official documentation here. The creators are actively working to enhance this compatibility and fix problems not only with Node.js Native API, but also with individual packages.

What about the speed of Bun.js?

Let’s take a look at the performance of Bun and Node.js through a few examples.

Writing and reading files

As an example, we’ll use a simple code for writing and reading files:

const fs = require('fs').promises;

const FILE_SIZES = [1024, 10 * 1024, 100 * 1024]; // 1KB, 10KB, 100KB
const FILE_NAME_TEMPLATE = 'testfile_{size}.txt';

async function writeFile(size) {
    const data = Buffer.alloc(size, 'a'); // creates a buffer filled with letter 'a'
    const fileName = FILE_NAME_TEMPLATE.replace('{size}', size);
    
    console.time(`Writing ${size} bytes`);

    try {
        await fs.writeFile(fileName, data);
        console.timeEnd(`Writing ${size} bytes`);
        console.log(`Written ${size} bytes to ${fileName}`);
    } catch (err) {
        throw new Error(`Error writing file: ${err.message}`);
    }
}

async function readFile(size) {
    const fileName = FILE_NAME_TEMPLATE.replace('{size}', size);
    
    console.time(`Reading ${size} bytes`);

    try {
        const data = await fs.readFile(fileName);
        console.timeEnd(`Reading ${size} bytes`);
        console.log(`Read ${data.length} bytes from ${fileName}`);
    } catch (err) {
        throw new Error(`Error reading file: ${err.message}`);
    }
}

async function performIOOperations() {
    for (const size of FILE_SIZES) {
        await writeFile(size);
        await readFile(size);
    }
}

performIOOperations()
    .then(() => console.log('I/O operations completed'))
    .catch((error) => console.error(`An error occurred: ${error.message}`));

When running this code in Node.js, we get the following results:

running this code in Node.js

The same code, when executed with Bun, will have these results:

The same code, when executed with Bun

Another thing that Bun runtime offers us out-of-the-box is the output formatting to the terminal.

The results show that Bun doesn’t always have the best performance compared to Node.js. In this particular example, we made no changes to the written code. However, Bun offers its API for working with files, including Bun.file(), Bun.write(), etc. You can read more about the API here. When using Bun’s native API, we got the following code:

//const fs = require('fs').promises;

const FILE_SIZES = [1024, 10 * 1024, 100 * 1024]; // 1KB, 10KB, 100KB
const FILE_NAME_TEMPLATE = 'testfile_{size}.txt';

async function writeFile(size) {
    const data = Buffer.alloc(size, 'a'); // creates a buffer filled with letter 'a'
    const fileName = FILE_NAME_TEMPLATE.replace('{size}', size);
    
    console.time(`Writing ${size} bytes`);

    try {
        await Bun.write(fileName, data);
        console.timeEnd(`Writing ${size} bytes`);
        console.log(`Written ${size} bytes to ${fileName}`);
    } catch (err) {
        throw new Error(`Error writing file: ${err.message}`);
    }
}

async function readFile(size) {
    const fileName = FILE_NAME_TEMPLATE.replace('{size}', size);
    
    console.time(`Reading ${size} bytes`);

    try {
        const data = await Bun.file(fileName);
        console.timeEnd(`Reading ${size} bytes`);
        console.log(`Read ${data.length} bytes from ${fileName}`);
    } catch (err) {
        throw new Error(`Error reading file: ${err.message}`);
    }
}

async function performIOOperations() {
    for (const size of FILE_SIZES) {
        await writeFile(size);
        await readFile(size);
    }
}

performIOOperations()
    .then(() => console.log('I/O operations completed'))
    .catch((error) => console.error(`An error occurred: ${error.message}`));

And the following results:

Utilizing Bun's native API significantly improved performance

Utilizing Bun’s native API significantly improved performance.

The server

Let’s assess how a simple test server with mocking data will work using Node.js and Bun.js. As an illustration, let’s take the following code:

const express = require('express');
const app = express();
const PORT = 3000;

// Sample in-memory data store
let posts = [
    { id: 1, title: 'First Post', content: 'This is the content of the first post.' },
    { id: 2, title: 'Second Post', content: 'This is the content of the second post.' },
    // Add more sample posts as needed...
];

// Middleware to parse JSON requests
app.use(express.json());

// Fetch all posts
app.get('/posts', (req, res) => {
    res.json(posts);
});

// Fetch a single post by ID
app.get('/posts/:id', (req, res) => {
    const post = posts.find(p => p.id === parseInt(req.params.id));
    if (!post) return res.status(404).send('Post not found.');
    res.json(post);
});

// Create a new post
app.post('/posts', (req, res) => {
    const post = {
        id: posts.length + 1,
        title: req.body.title,
        content: req.body.content,
    };
    posts.push(post);
    res.status(201).json(post);
});

// Update a post by ID
app.put('/posts/:id', (req, res) => {
    const post = posts.find(p => p.id === parseInt(req.params.id));
    if (!post) return res.status(404).send('Post not found.');

    post.title = req.body.title || post.title;
    post.content = req.body.content || post.content;
    
    res.json(post);
});

// Delete a post by ID
app.delete('/posts/:id', (req, res) => {
    posts = posts.filter(p => p.id !== parseInt(req.params.id));
    res.status(204).send();
});

app.listen(PORT, () => {
    console.log(`Server running at <http://localhost>:${PORT}/`);
});

Here’s the result for Node.js:

 result for Node.js:

And for Bun:

result for Bun

Even though this server lacks complex logic and operates locally, we can still observe better performance compared to Node.js.

Working with data

Let’s take an example where we need to process a large object. For this purpose, I created a 45 MB JSON file containing an array of objects like this:

{
    "userId": "b1ba31ac-25ce-4432-8e9f-b4cd89da167a",
    "session": "a0dd197b-22bb-4934-8c60-2408912a2a16",
    "timestamp": "2023-09-08T07:43:00.421Z",
    "activity": "LOGOUT",
    "ip": "214.76.47.2",
    "userAgent": "Mozilla/5.0 (X11; Linux i686; rv:7.9) Gecko/20100101 Firefox/7.9.5",
    "location": "Mohammadchester, Virginia, Andorra"
  },

Here is the code for Node.js:

import fs from 'fs/promises';

const logs = JSON.parse(await fs.readFile('../userLogs.json', 'utf-8'));

function detectWindowsUsers(logs) {
    return logs.filter(log => log.userAgent.includes('Windows'));
}

console.time('detectWindowsUsers');

const windowsUsers = detectWindowsUsers(logs);

console.timeEnd('detectWindowsUsers');

Running it, we got:

code

For Bun, we used the following code:

const logs = JSON.parse(await Bun.file('../userLogs.json', 'utf-8').text());

function detectWindowsUsers(logs) {
    return logs.filter(log => log.userAgent.includes('Windows'));
}

console.time('detectWindowsUsers');

const windowsUsers = detectWindowsUsers(logs);

console.timeEnd('detectWindowsUsers');

And we got the following result:

code 2

In this test, the results were nearly identical.

Regular expressions

On the same file, we ran this code:

import fs from 'fs/promises';

const logs = JSON.parse(await fs.readFile('../userLogs.json', 'utf-8'));

function detectIPv4(logs) {
    const ipv4Pattern = /\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b/g;
    const allIPs = logs.map(log => log.ip.match(ipv4Pattern)).flat();
    return [...new Set(allIPs)];  // Unique IPs
}

function detectChromeUsers(logs) {
    return logs.filter(log => /Chrome/.test(log.userAgent));
}

// Benchmark IPv4 Detection
console.time('IPv4 Detection');
const ipv4Addresses = detectIPv4(logs);
console.timeEnd('IPv4 Detection');
console.log(`Detected ${ipv4Addresses.length} unique IPv4 addresses.`);

// Benchmark Chrome User Detection
console.time('Chrome User Detection');
const chromeUsers = detectChromeUsers(logs);
console.timeEnd('Chrome User Detection');
console.log(`Detected ${chromeUsers.length} Chrome users.`);

Here’s the result for Node.js:

the result for Node.js:

And for Bun (with corresponding changes in the file reading):

Bun showed a much better performance

In this case, Bun showed a much better performance.

So, what’s the verdict?

Bun is indeed a young and highly ambitious technology. To capture the audience’s attention, its creators have chosen catchy slogans. The promise is a swift Node.js replacement with Bun, backward compatibility with most of the Node.js ecosystem, and a significant performance boost — isn’t that impressive?

While it might seem impressive in promotional videos, real-world project implementation reveals a different reality. In practice, we are witnessing the following: replacing Node.js with Bun isn’t always straightforward and requires a lot of effort; performance surpasses Node.js in many aspects, but not always; and some npm packages work, while others — don’t. At the time of writing this article, the Bun repository had more than 1800 open tickets.

These are common challenges for a young technology and the developers behind Bun.js have put remarkable effort into it. But, there’s still a lot more to accomplish. Besides simply fixing bugs, it is necessary to build a strong community that will provide ongoing support and further develop Bun. Despite its popularity, the list of significant contributors is relatively short, with the primary contributor being Bun’s creator:The dynamics of Bun’s contributors community developmentFigure 2. The dynamics of Bun’s contributors community development

It’s also important that any large companies willing to take a chance on this technology actively contribute to developing pertinent marketing cases to promote its popularization further.

So for now, Bun is merely a modest local uprising with the potential to transform into a revolution. While you can experiment with Bun for specific utilities or small sub-projects today, it’s not quite time to completely migrate and use it for large codebases.

Fancy an in-depth discussion of why Node.js isn’t facing any hurdles in the future and might be one of the most stable tech options for your project? Schedule a one-on-one session with Oleksandr by filling out the contact form.

Other articles

or

Book a meeting

Zoom 30 min

or call us+1 (800) 917-0207

Ready to innovate your business?

We are! Let’s kick-off our journey to success!