Should You Use Bun In 2024?

Should You Use Bun In 2024?

Published:

Virtually everyone in the JavaScript community is hyped about Bun. But is this only because of the superior performance compared to NodeJS? In this article, I will share my experience with Bun so far.

Cover image

When I started using Bun for rebuilding my blog, I was really impressed by its performance. And not only this, The convenient tooling and API that comes with it made the work a great experience.

But there was also some friction during adopting it.

At the end of this post, you should be able to decide whether you want to give it a shot as well.

Performance

As Bun is primarily focused on performance, it really shines in this area. It installs packages faster than every NodeJS-based package manager.

The startup time is great and all the IO operations are faster than what we are used to in NodeJS.

When looking at a hello world example of the HTTP stack, according to this benchmark , Bun can even compete with Go.

This said, in Bun, the JavaScript code is still JIT-compiled and it is a very high-level language. It is also single-threaded. So if you are building a service that executes CPU-intensive tasks, you may be better advised to pick a compiled, multithreaded language, like Go or even Rust.

But if you are mostly doing IO, like communicating to APIs, Databases and file system, Bun performance-wise is likely to be a good choice.

Compatibility to NodeJS API

Bun is built to be a drop-in replacement for NodeJS. Most parts of the NodeJS API are implemented, and the migration to Bun should be easy for most small to medium-sized projects. However, there are cases where it’s not ready to replace NodeJS.

I cannot use Bun for building my Qwik app for production in my docker build, so NodeJS / npm will still be part of my build pipeline. Deploying the production version within a bun container works, however.

This is what my Dockerfile looks like.

FROM node:20-slim AS build
ADD packages/app package-lock.json /app/
ADD packages/content /content/
WORKDIR /app
RUN npm install &&
    npm run build

FROM oven/bun:1.0.18-alpine
WORKDIR /app
COPY --from=build /app/dist /app/dist
COPY --from=build /app/server /app/server
ENTRYPOINT [ "bun", "server/entry.bun.js" ]

I also had trouble installing sharp , which I use for image optimization. Luckily, this was fixed in version 1.0.17 of Bun, and I don’t have to use npm anymore to install my packages for my content service.

All in all, the support for existing npm packages is getting better, and I think the best way to find out whether it works with your dependencies is to just try it out. Since the NodeJS API is mostly implemented, there is quite a chance that your project runs just fine.

Bun CLI

The command line interface is quite different from NodeJS’ CLI, and some things are more convenient.

For example, the bun init -y command not only creates a package.json and a README file for you, but it automatically sets up a tsconfig.json file. This is a nice feature for those of us who are using typescript.

Another convenience is that you can directly turn on watch mode via, bun --watch start . This will restart our Bun process on any module change, which is a must-have for development. In NodeJS, you would need to have a separate package for watching the module dependency graph, like nodemon or esbuild .

Bun API

As stated before, Bun implements a large portion of the NodeJS API. While this is important for an easy switch, it also comes with its own API extension. This Bun API is exposed via the Bun global object, the bun module and a set of modules, prefixed with bun: .

This API is generally more abstract than NodeJS API.

Bun.file for example, creates a BunFile that implements the Blob interface. It allows you to easily read out a file like this:

const foo = Bun.file("package.json");

const text = await foo.text();
const parsed = await foo.json();
const stream = await foo.stream();
const buffer = await foo.arrayBuffer();

An equivalent solution in Node would look like this:

import { createReadStream, promises as fs } from "fs";

const text = await fs.readFile("package.json", "utf-8");
const json = JSON.parse(
  await fs.readFile("package.json", "utf-8"),
);
const stream = createReadStream("package.json");
const buffer = (await fs.readFile("package.json")).buffer;

The Bun.serve API allows you to easily spawn an HTTP server:

Bun.serve({
  fetch(req) {
    return new Response("Bun!");
  },
});

This API is quite different from Node’s: You create a Response within the handler, instead of getting it passed in.

import { createServer } from "http";

createServer((req, res) => {
  res.end("Node!");
}).listen(3000);

So in NodeJS you are provided with a response object, and you have to pass it through to your request handling logic to mutate it.

The new API is used by Elysia, for instance. It provides a high-level API with routing and everything you need to build an application server. The API is similar to the one of express, but even more convenient. Of course, it performs much better than express and even fastify, which was my favorite until now.

More about Elysia may be the topic of another post.

Extended Capabilities

Bun not only simplifies and improves some parts of the NodeJS API, but it also extends it to handle some very common tasks in a performance-optimized way.

One example is parsing .env files to extend your local environment variables configuration: Bun simply picks it up if it exists. In NodeJS you would use the dotenv npm package.

Then there is the SQLite API that is inspired by the better-sqlite npm package. However, it is multiple times faster in accessing the database, and we don’t need to install any package.

Testing also made it to the Bun API.

High-performance Testing

Since testing is provided directly by Bun, it is highly optimized for performance. The tests are executed multiple times faster than the ones running with Jest or Vitest.

It also provides watch mode, and already covers a good portion of Jest’s API already.

In my opinion, it makes sense to have testing directly embedded within Bun because everyone should write tests, anyway.

Native TypeScript support

As a TypeScript developer, one little annoyance when setting up a new NodeJS project is installing and configuring TypeScript. I preferred using esbuild over ts-node until now. So I need to install and configure at least two libraries.

In Bun, TypeScript can directly be executed. It directly generates a .tsconfig for us, if we run the bun init command to bootstrap our projects.

And watch mode works out-of-the box!

Big plus for this feature!

More than just a JS runtime

Bun is not only a JavaScript runtime. It comes with all the nice stuff that you need to develop, test and bundle your applications. You don’t need to run a command like npm install -d esbuild typescript vitest to install all the tools necessary and then configure all those tools to start a serious project.

To run your tests, you can simply use bun test . In the package bun:test we find everything we need to test our apps.

Bun is also a bundler. You can bundle your app, and you can even compile it to a single executable file.

Should you give it a try?

I really don’t think Bun is just a hype, but the superior server-side JavaScript runtime, compared to NodeJS, which is dependent on V8’s performance.

At the time of writing, there are some npm libraries that don’t work with Bun yet, but I’m confident that those gaps will eventually be closed. And if they are vital to the project, we can think about placing them in a separate node process.

I recommend building new projects with Bun because it has its new concise, high-level, high-performance API, which lets you get stuff done more quickly and reduces sources of potential errors. Moreover, the development experience is great, especially with the performance boost in startup time, watch mode and testing.

If NodeJS performance is the bottleneck in your existing applications, you could try a time-slotted migration to Bun. It could be a low-hanging fruit to perform the switch.

Never stop learning and Happy Coding!