React Native Mastery - Launched!Master React Native and Expo by building 7 real-world projects

Exploring Bun 1.0: Is it really faster than NodeJS?

Vadim Savin profile picture
Vadim SavinApr 21, 2024

Intro

Bun 1.0 is finally here.

Let’s get our hands dirty and explore the possibilities that Bun is bringing to the table. I am also excited to test out some of the promises Bun is making around performance. Is it really that fast? 🤔

What is Bun?

Bun is an all-in-one toolkit to build and run Javascript and Typescript applications. It’s the Swiss knife that can do everything: run, build, test, debug, transpile, test. And the best part about it is that it’s really fast ⚡

Install Bun

Let’s follow the instructions on Bun’s Homepage to install it on our system.

Or simply run: curl -fsSL https://bun.sh/install | bash

Currently, bun is supported only on macOS and Linux systems.

Bun as a package manager

The easiest way to get started using bun is by replacing your existing package manager npm or yarn with bun. You will see instant benefits and much faster install speed.

It works with any javascript project. If you have a package.json file, you can start using bun install and drastically improve the speed of installing and managing packages.

Installing a package

Running npm install express took 5s while bun install express did it almost 10 times faster - in 0.6s.

1._npm_install_express.png

2._bun_install_express.png

Initializing an Expo app

Initializing an expo app feels like an eternity using npm. In this case, we can see that it took 60s.

3._npx_create_expo.png

The same project, initialized with bun, took only 7s.

4._bun_create_expo.png

Bun Runtime

Bun is a drop-in replacement for NodeJS that is designed for speed.

Let’s put this to a test by running a NodeJS Fullstack app with Bun, and do some load testing to see which one is faster.

We will use this Fullstack app from Prisma templates. It has a Rest API built with Express on the backend, and a NextJS app on the frontend.

Let’s clone the repository

BASH
git clone git@github.com:prisma/prisma-examples.git --depth=1

Navigate to the rest-nextjs-express project and open it in VSCode

BASH
cd prisma-examples/typescript/rest-nextjs-express

Benchmark with NodeJS

Let’s start our test using NodeJS and NPM to install and run the 2 apps from our project: the front end and the back end.

Backend
  1. Navigate to the backend, install dependencies
BASH
cd backend
npm install

5._backend_npm_install.png

  1. Seed database
BASH
npx prisma migrate dev --name init
  1. Start the server
BASH
npm run dev
Frontend

In a separate terminal, let’s navigate to the frontend, install dependencies and run the server

BASH
cd frontend
npm install
npm run dev

7._frontend_npm_install.png

Now, you can navigate to http://localhost:3000/ and see your blog website.

Untitled.png

If you have an error related to the <Link /> component, find in the project (frontend/components/Header.tsx) the <Link href="/create"> and make it <Link legacyBehavior href="/create">.

Hello World endpoint

Let’s add a simple endpoint that will return Hello World. Inside the backend/source/index.ts add:

BASH
app.get('/', async (req, res) => {
res.send('Hello world')
})
Load testing

Our Rest API is running on http://localhost:3001.

We will use a tool called Bombardier to send 100k requests to the server from 125 concurrent connections.

Hello world:

BASH
bombardier -c 125 -n 100000 http://localhost:3001

8._bombardier_hello_world_NodeJS.png

Let’s also try a more complex query, the get feed endpoint:

BASH
bombardier -c 125 -n 100000 http://localhost:3001/feed

9._bombardier_feed_NodeJS.png

Benchmark with Bun

Let’s do all the above steps with Bun and see if it is that easy to use Bun as a drop-in replacement for NodeJS, and what performance benefits we will get.

Let’s clone the repository again

BASH
git clone git@github.com:prisma/prisma-examples.git --depth=1 bun-prisma-examples

Navigate to the rest-nextjs-express project and open it in VSCode

BASH
cd bun-prisma-examples/typescript/rest-nextjs-express

Backend

  1. Navigate to the backend, and install dependencies using bun
BASH
cd backend
bun install

10._backend_bun_install.png

  1. Seed database
BASH
bunx prisma migrate dev --name init
  1. Start the server
BASH
bun src/index.ts

Frontend

In a separate terminal, let’s navigate to the front and install dependencies

BASH
cd frontend
bun install

11._frontend_bun_install.png

Now, we can run our NextJS app using bun by running

BASH
bun --bun run dev

If we just run bun run dev, it will run the dev server with Node.js

Now, you can navigate to http://localhost:3000/ and see your blog website. The hot reloading is still working when we make any changes to our files.

Hello World endpoint

Let’s add a simple endpoint that will return Hello World. Inside the backend/source/index.ts add:

BASH
app.get('/', async (req, res) => {
res.send('Hello world')
})

Load testing

Hello world:

BASH
bombardier -c 125 -n 100000 http://localhost:3001

12._bombardier_hello_world_Bun.png

Let’s also try a more complex query, the get feed endpoint:

BASH
bombardier -c 125 -n 100000 http://localhost:3001/feed

9._bombardier_feed_NodeJS.png

Benchmarking outcomes

If we compare the NodeJS and Bun during this load testing, we see that Bun has processed the requests roughly twice as fast as NodeJS.

Native Bun HTTP Server vs NodeJS

Let’s create a new folder where we will create 2 simple http servers. One with NodeJS and one with Bun.

BASH
mkdir http-servers
cd http-servers
mkdir nodejs
mkdir bun
code .

NodeJS server

Create the nodejs/index.js file and create a simple HTTP server

BASH
var http = require('http');
http.createServer(function (req, res) {
res.write('Hello World!');
res.end();
}).listen(3000);

And run the server with node nodejs/index.js

Bun server

BASH
const server = Bun.serve({
port: 3001,
fetch(request) {
return new Response('Welcome to Bun!');
},
});
console.log(`Listening on localhost:${server.port}`);

To run it, we will use bun bun/index.js

Load testing

Let’s load-test them with 1M requests from 124 concurrent connections.

NodeJS took 16s for all requests, and the maximum requests per second were 72k.

14._Bobardier_NodeJS_HTTP.png

Bun took 10s for all requests, and the maximum request per second where almost twice more: 137k.

Untitled.png

Bun’s ecosystem

Typescript support out of the box

We can simply rename from index.js to index.ts and bun will run the server without additional dependencies.

You can also run bun init inside the bun project, to set up the basic project structure and typescript config.

Read more: https://bun.sh/docs/typescript

Files

https://bun.sh/docs/api/file-io#reading-files-bun-file

Bun provides a set of optimized APIs for reading and writing files.

TYPESCRIPT
const file = Bun.file('hello.txt');
await file.text();

JSON support

Having a posts.json file with a list of posts:

TYPESCRIPT
[
{ "id": 1, "title": "Hello" },
{ "id": 2, "title": "Bye" }
]

We can use it inside Bun in 2 different ways:

Option 1: Read the file

TYPESCRIPT
const file = Bun.file('posts.json');
...
return Response.json(await file.json());

Option 2: import json

TYPESCRIPT
import posts from './posts.json';
...
return Response.json(posts);

SQLite

https://bun.sh/docs/api/sqlite

Bun natively implements a high-performance SQLite3 driver. To use it import from the built-in bun:sqlite module.

Import and create a new SQLite database

TYPESCRIPT
import { Database } from 'bun:sqlite';
const db = new Database('mydb.sqlite');

Create a new table history that will contain logs of the requests made to the server.

TYPESCRIPT
db.query(
`
CREATE TABLE IF NOT EXISTS history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
datetime TEXT NOT NULL DEFAULT (datetime('now')),
url TEXT NOT NULL
);
`
).run();

Now, we can insert new rows and query them from the SQLite database

TYPESCRIPT
async fetch(request) {
const url = new URL(request.url);
const query = db.query('INSERT INTO history (url) VALUES (?1);');
query.run(url.pathname);
const allHistory = db.query('SELECT * FROM history ORDER BY id DESC').all();
return Response.json(allHistory);
},

Password hashing

Bun.password is a collection of utility functions for hashing and verifying passwords with various cryptographically secure algorithms.

TYPESCRIPT
const password = 'super-secure-pa$$word';
const hash = await Bun.password.hash(password);
console.log(hash);
const isMatch = await Bun.password.verify(password, hash);
console.log(isMatch);

More about hashing read here.

Conclusion

I am quite impressed by the performance of Bun. What’s also noteworthy, is that there is nothing much new we have to learn about Bun in order to start using it. It’s a great drop-in replacement for NodeJS, and it is supposed to work as-is for most use cases.


Vadim Savin profile picture

Vadim Savin

Hi 👋 Let me introduce myself

I started my career as a Fullstack Developer when I was 16 y.o.

In search of more freedom, I transitioned to freelancing, which quickly grew into a global software development agency 🔥

Because that was not challenging enough, I started my startup which is used by over 20k users. This experience gave another meaning to being a (notJust) developer 🚀

I am also a proud ex-Amazon SDE and Certified AWS Architect, Developer and SysOps. You are in good hands 👌