Introduction
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.
Initializing an Expo app
Initializing an expo app feels like an eternity using npm. In this case, we can see that it took 60s.
The same project, initialized with bun, took only 7s.
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
git clone git@github.com:prisma/prisma-examples.git --depth=1
Navigate to the rest-nextjs-express project and open it in VSCode
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
- Navigate to the backend, install dependencies
cd backendnpm install
- Seed database
npx prisma migrate dev --name init
- Start the server
npm run dev
Frontend
In a separate terminal, let’s navigate to the frontend, install dependencies and run the server
cd frontendnpm installnpm run dev
Now, you can navigate to http://localhost:3000/ and see your blog website.
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:
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:
bombardier -c 125 -n 100000 http://localhost:3001
Let’s also try a more complex query, the get feed endpoint:
bombardier -c 125 -n 100000 http://localhost:3001/feed
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
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
cd bun-prisma-examples/typescript/rest-nextjs-express
Backend
- Navigate to the backend, and install dependencies using bun
cd backendbun install
- Seed database
bunx prisma migrate dev --name init
- Start the server
bun src/index.ts
Frontend
In a separate terminal, let’s navigate to the front and install dependencies
cd frontendbun install
Now, we can run our NextJS app using bun by running
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:
app.get('/', async (req, res) => {res.send('Hello world')})
Load testing
Hello world:
bombardier -c 125 -n 100000 http://localhost:3001
Let’s also try a more complex query, the get feed endpoint:
bombardier -c 125 -n 100000 http://localhost:3001/feed
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.
mkdir http-serverscd http-serversmkdir nodejsmkdir buncode .
NodeJS server
Create the nodejs/index.js file and create a simple HTTP server
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
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.
Bun took 10s for all requests, and the maximum request per second where almost twice more: 137k.
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.
const file = Bun.file('hello.txt');await file.text();
JSON support
Having a posts.json file with a list of posts:
[{ "id": 1, "title": "Hello" },{ "id": 2, "title": "Bye" }]
We can use it inside Bun in 2 different ways:
Option 1: Read the file
const file = Bun.file('posts.json');...return Response.json(await file.json());
Option 2: import json
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
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.
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
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.
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.