DEV Community

Cover image for Bun vs Node.js: Everything you need to know
Yoav Ganbar for Builder.io

Posted on • Originally published at builder.io on

Bun vs Node.js: Everything you need to know

Written by Vishwas Gopinath

On September 8th, there was fresh buzz in the JavaScript community: Bun v1.0, created by Jarred Sumner, had arrived. But with all the talk, many are left wondering: What's the essence of Bun? Why is everyone drawing parallels with the tried-and-true Node.js? Is Bun just another fleeting trend, or is it here to redefine the game? In this article, let’s dive deep into Bun, check out its features, and find out how it compares to the well-established Node.js.

What is Bun?

Bun is a super fast all-in-one toolkit for JavaScript and TypeScript apps. The beauty of Bun lies in its ability to streamline the development process, making it smoother and more efficient than ever. This is possible because Bun is not just a runtime, it's also a package manager, a bundler, and a test runner. Imagine having a Swiss Army knife for JS development; that's Bun for you.

What Bun solves for

The inception of Node.js in 2009 was groundbreaking. However, as with many technologies, as it grew, so did its complexity. Think of it like a city. As a city expands, traffic congestion can become a problem.

Bun aims to be the new infrastructure that alleviates this congestion, making things run smoother and faster. It's not about reinventing the wheel but refining it, ensuring that while we get the speed and simplicity, we don't lose the essence of what makes JavaScript unique and powerful.

Bun is designed as a faster, leaner, more modern replacement for Node.js so let’s take a closer look at some comparison. But first let’s discuss one other topic.

Node.js versus Deno versus Bun

When discussing the evolution of JavaScript runtimes, it's hard to overlook Deno. Ryan Dahl, the creator of Node.js, introduced Deno as a new runtime that aimed to address some of the challenges and regrets he identified with Node.js.

Deno is a secure runtime for JavaScript and TypeScript. It directly addresses many of the shortcomings of Node.js. For instance, Deno natively supports TypeScript without the need for external tools. Unlike Node.js, where scripts have broad permissions by default, Deno adopts a security-first approach requiring developers to explicitly grant permissions for potentially sensitive operations, such as file system access or network connectivity.

While Deno presents a compelling alternative to Node.js, it hasn't matched Node.js's widespread adoption. Therefore, this article centers on contrasting Bun with the well-established Node.js.

Getting started

With Bun, we can scaffold an empty project with the command bun init -y. We have a few files generated and in index.ts, add a line, console.log("Hello, Bun!"). In the terminal, run the command bun index.ts to see “Hello, Bun!” logged.

Bun versus Node.js: JavaScript runtime

A JavaScript runtime is an environment which provides all the necessary components in order to use and run a JavaScript program.

Both Node.js and Bun are runtimes. Node.js is primarily written in C++ where as Bun is written with a low-level general purpose programming language called Zig. But that is just the tip of the ice berg. Let’s take a closer look at other differences when treating Bun as a runtime alone.

JavaScript engine

A JavaScript engine is a program that converts JavaScript code we write into machine code that allows a computer to perform specific tasks.

While Node.js uses Google's V8 engine that power's Chrome browser, Bun uses JavaScriptCore (JSC), which is an open source JavaScript engine developed by Apple for Safari.

V8 and JSC have different architectures and optimization strategies. JSC prioritizes faster start times and reduced memory usage with a slightly slower execution time. On the other hand, V8 prioritizes fast execution with more runtime optimization which may lead to more memory usage.

This makes Bun fast, starting up to 4x faster than Node.js.

Summary: bun ran 2.19 times faster than deno and 4.81 times faster than node

Summary: bun ran 2.19 times faster than deno and 4.81 times faster than node

Transpiler

While Node.js is a powerful runtime for JavaScript, it doesn't natively support TypeScript files. To execute TypeScript in a Node.js environment, external dependencies are required. One common approach is to use a build step to transpile TypeScript (TS) into JavaScript (JS) and then run the resulting JS code. Here's a basic setup that uses the ts-node package:

1. Installation

npm install -D typescript ts-node
Enter fullscreen mode Exit fullscreen mode

2. Script configuration

In your package.json, you can set up scripts to streamline the process:

{
  "scripts": {
    "start": "ts-node ./path/to/your/file.ts"
  }
}
Enter fullscreen mode Exit fullscreen mode

3. Execution

With the above script, you can easily run your TypeScript file:

npm start
Enter fullscreen mode Exit fullscreen mode

In contrast, Bun offers a more streamlined approach. It comes with a JavaScript transpiler integrated into the runtime. This allows you to directly run .js, .ts, .jsxand .tsx files. Bun's built-in transpiler seamlessly converts these files to vanilla JavaScript, facilitating immediate execution without additional steps.

bun index.ts
Enter fullscreen mode Exit fullscreen mode

The difference in speed is magnified when running a TypeScript file as Node.js requires a transpilation step before it can be run.

Bun takes 8ms, Node esbuild 40ms, tsx 120ms and tsx 350ms

ESM and CommonJS compatibility

A module system allows developers to organize code into reusable segments. In JavaScript, the two primary module systems are CommonJS and ES modules (ESM). CommonJS, originating from Node.js, uses require and module.exports for synchronous module handling, ideal for server-side operations.

ESM, introduced in ES6, employs import and export statements, providing a more static and asynchronous approach, optimized for browsers and modern build tools. Let's use colors for CommonJS and chalk for ESM, two popular packages for adding colored outputs to the console and to better understand the module systems.

Node.js has been traditionally associated with the CommonJS module system. Here's a typical usage:

// CommonJS in Node.js (index.js)
const colors = require("colors");
console.log(colors.green('Hello, world!'));
Enter fullscreen mode Exit fullscreen mode

For ES modules in Node.js, you have one of two options:

  1. You need to include "type": "module" in your package.json.
  2. Use the .mjs extension.
// ESM in Node.js (index.mjs)
import chalk from 'chalk';
console.log(chalk.blue('Hello, world!'));
Enter fullscreen mode Exit fullscreen mode

The transition from CommonJS to ES modules (ESM) has been a complex journey. It took Node.js half a decade post ESM's introduction to support it without the experimental flag. Still, CommonJS remains prevalent in the ecosystem.

Bun simplifies the module system by supporting both without any special configuration. Bun's standout feature is its ability to support both import and require() in the same file, something not natively possible in Node.js:

// Mixed modules in Bun (index.js)
import chalk from "chalk";
const colors = require("colors");

console.log(chalk.magenta('Hello from chalk!'));
console.log(colors.cyan('Hello from colors!'));
Enter fullscreen mode Exit fullscreen mode

Web APIs

Web APIs, integral to browser-based applications, offer tools like fetch and WebSocket for web interactions. While these have become browser standards, their support in server-side environments like Node.js has been inconsistent.

In earlier versions of Node.js, Web standard APIs commonly available in browsers weren't natively supported. Developers had to rely on third-party packages like node-fetch to replicate this functionality. However, from Node.js v18, there's experimental support for the fetch API, potentially eliminating the need for these packages.

Bun simplifies this by offering built-in support for these Web standard APIs. Developers can directly use stable fetch, Request, Response, WebSocket and other browser-like APIs without any additional packages. Furthermore, Bun's native implementation of these Web APIs ensures they are faster and more reliable compared to third-party alternatives.

Here's an example compatible with both Node.js (v18 and above) and Bun. While it's experimental in Node.js, the same functionality is stable in Bun:

// Experiment fetch in Node.js (v18 and above) and built-in fetch in Bun
async function fetchUserData() {
  const response = await fetch("https://jsonplaceholder.typicode.com/users/1");
  const user = await response.json();
  console.log(user.name);
}

fetchUserData(); // Leanne Graham
Enter fullscreen mode Exit fullscreen mode

Hot reloading

Hot reloading is a feature that boosts developer productivity by automatically refreshing or reloading parts of an application in real-time as the code changes, without requiring a full restart.

In the Node.js ecosystem, you have a couple of options for achieving hot reloading. One popular tool has been nodemon, which hard-restarts the entire process:

nodemon index.js
Enter fullscreen mode Exit fullscreen mode

Alternatively, starting from Node.js v18, there's an experimental --watch flag introduced:

node --watch index.js
Enter fullscreen mode Exit fullscreen mode

Both methods aim to provide real-time reloading of the application as code changes. However, they might have different behaviors, especially in certain environments or scenarios.

For instance, nodemon can lead to disruptions like disconnecting HTTP and WebSocket connections, while the --watch flag, being experimental, might not offer the full suite of features and has some reported issues in the GitHub issues.

Bun takes hot reloading a step further. By running Bun with the --hot flag, hot reloading is enabled:

bun --hot index.ts
Enter fullscreen mode Exit fullscreen mode

Unlike the Node.js methods that might require a full process restart, Bun reloads your code in-place without terminating the old process. This ensures that HTTP and WebSocket connections remain uninterrupted and the application state is preserved, providing a smoother development experience.

Node.js compatibility

When transitioning to a new runtime or environment, compatibility is often a primary concern for developers. Bun addresses this by positioning itself as a drop-in replacement for Node.js. This means that existing Node.js applications and npm packages can seamlessly integrate with Bun without any modifications. Key features that ensure this compatibility include:

  • Support for built-in Node.js modules such as fs, path, and net.
  • Recognition of global variables like __dirname and process.
  • Adherence to the Node.js module resolution algorithm, including the familiar node_modules structure.

Bun is still evolving. It's tailored to enhance development workflows and is ideal for environments where resources are limited, such as serverless functions. The team behind Bun is striving for comprehensive Node.js compatibility and better integration with prevalent frameworks

While Bun ensures compatibility with Node.js, it doesn't stop there. Bun ships with highly-optimized, standard-library APIs for the things you need most as a developer.

Bun APIs

Bun.file()

Lazily load files and access their content in various formats. This method is up to 10x faster than its Node.js counterpart.

// Bun (index.ts)
const file = Bun.file("package.json");
await file.text();

// Node.js (index.mjs)
const fs = require("fs/promises");
const fileContents = await fs.readFile("package.json", "utf-8");
Enter fullscreen mode Exit fullscreen mode

Bun.write()

A versatile API to write data to disk, from strings to Blobs. It writes up to 3x faster than Node.js.

// Bun (index.ts)
await Bun.write("index.html", "<html/>");

// Node.js (index.mjs)
const fs = require("fs/promises");
await fs.writeFile("index.html", "<html/>");
Enter fullscreen mode Exit fullscreen mode

Bun.serve()

Set up HTTP server or WebSocket server using Web-standard APIs. It can serve 4x more requests per second than Node.js and handle 5x more WebSocket messages than the ws package in Node.js. This backend capability is reminiscent of how developers use Express in Node.js but with the added benefits of Bun's performance optimizations.

// Bun (index.ts)
Bun.serve({
  port: 3000,
  fetch(request) {
    return new Response("Hello from Bun!");
  },
});

// Node.js (index.mjs)
import http from "http";
const server = http.createServer((req, res) => {
  res.writeHead(200, { "Content-Type": "text/plain" });
  res.end("Hello from Node.js!");
});
server.listen(3000);
Enter fullscreen mode Exit fullscreen mode

Bun also has support for sqlite and password built-in.

Bun versus Node.js: package manager

Bun is more than just a runtime; it's an advanced toolkit that includes a powerful package manager. If you've ever found yourself patiently waiting during dependency installations, Bun offers a refreshingly faster alternative. Even if you don't use Bun as a runtime, its built-in package manager can speed up your development workflow.

Check out this table comparing Bun commands with npm, Node's package manager:

At a glance, Bun's commands might seem familiar but the experience is anything but ordinary. Bun boasts installation speeds that are orders of magnitude faster than npm. It achieves this by leveraging a global module cache, eliminating redundant downloads from the npm registry. Additionally, Bun employs the fastest system calls available for each operating system, ensuring optimal performance.

Here's a speed comparison of installing dependencies for a starter Remix project from cache, comparing Bun and npm:

Bun takes 0.36s, pnpm 6.44s, npm 10.58s and yarn 12.08s

The bun CLI contains a Node.js-compatible package manager designed to be a dramatically faster replacement for npm, yarn, and pnpm

Moreover, a bun run <command> takes just 7ms, while npm run <command> takes 176ms. While Node.js's npm has been the standard for JavaScript package management for years, Bun really is a speed powerhouse and presents a compelling alternative.

Bun versus Node.js: bundler

Bundling is the process of taking multiple JavaScript files and merging them into one or more optimized bundles. This process can also involve transformations, such as converting TypeScript to JavaScript or minifying the code to reduce its size.

In the Node.js ecosystem, bundling is typically handled by third-party tools rather than Node.js itself. Some of the most popular bundlers in the Node.js world include Webpack, Rollup, and Parcel, offering features like code splitting, tree shaking, and hot module replacement.

Bun, on the other hand, is not just a runtime and a package manager but also a bundler in its own right. It's designed to bundle JavaScript and TypeScript code for various platforms, including frontend applications in the browser (React or Next.js applications) and Node.js.

To bundle with Bun, you can use a simple command:

bun build ./index.ts --outdir ./build
Enter fullscreen mode Exit fullscreen mode

This command bundles the index.ts file and outputs the result in the ./build directory. The bundling process is incredibly fast, with Bun being 1.75x faster than esbuild, and significantly outpacing other bundlers like Parcel and Webpack.

Bun takes 0.17s, esbuild 0.3s, rspack 4.45s, Parcel 2 26.32s, Rollup 32s and Webpack 5 38.02s

Bun takes 0.17s, esbuild 0.3s, rspack 4.45s, Parcel 2 26.32s, Rollup 32s and Webpack 5 38.02s

A standout feature in Bun is its introduction of JavaScript macros. These allow for the execution of JavaScript functions during bundling, with the results directly inlined into the final bundle. This mechanism offers a fresh perspective on bundling.

Check out this example where Bun's JavaScript macros are leveraged to fetch a username during the bundling process. Instead of making a runtime API call, the macro fetches the data at bundle-time, inlining the result directly into the final output:

// users.ts

export async function getUsername() {
  const response = await fetch("https://jsonplaceholder.typicode.com/users/1");
  const user = await response.json();
  return user.name;
}

// index.ts

import { getUsername } from "./users.ts" with { type: "macro" };
const username = await getUsername();

// build/index.js

var user = await "Leanne Graham";
console.log(user);
Enter fullscreen mode Exit fullscreen mode

While Node.js has its established bundling tools, Bun offers an integrated, faster, and innovative alternative that could reshape the bundling landscape.

Bun versus Node.js: test runner

Testing is a crucial aspect of software development, which ensures that code behaves as expected and catches potential issues before they reach production. In addition to being a runtime, a package manager and a bundler, Bun is also a test runner.

While Node.js developers have traditionally relied on Jest for their testing needs, Bun introduces a built-in test runner that promises speed, compatibility, and a range of features that cater to modern development workflows.

Bun's test runner, bun:test, is designed to be fully compatible with Jest, a testing framework known for its "expect"-style APIs. This compatibility ensures that developers familiar with Jest can easily transition to Bun without a steep learning curve.

import { test, expect } from "bun:test";

test("2 + 2", () => {
  expect(2 + 2).toBe(4);
});
Enter fullscreen mode Exit fullscreen mode

Executing tests is straightforward with the bun test command. Moreover, Bun's runtime supports TypeScript and JSX out of the box, eliminating the need for additional configurations or plugins.

Migrating from Jest or Vitest

Bun's commitment to compatibility shines through its support for Jest's global imports. For instance, importing from @jest/globals or vitest will be internally re-mapped to bun:test. This means that existing test suites can run on Bun without any code modifications.

// index.test.ts
import { test } from "@jest/globals";

describe("test suite", () => {
  test("addition", () => {
    expect(1 + 1).toBe(2);
  });
});
Enter fullscreen mode Exit fullscreen mode

Performance benchmarks

Bun's test runner is not just about compatibility; it's about speed. In a benchmark against the test suite for Zod, Bun proved to be 13x faster than Jest and 8x faster than Vitest. This speed advantage is further highlighted by Bun's matchers, which are implemented in fast native code. For instance, expect().toEqual() in Bun is a staggering 100x faster than Jest and 10x faster than Vitest.

Whether you're looking to migrate existing tests or start a new project, Bun provides a robust testing environment that aligns with modern development needs.

Conclusion

Node.js has long been a cornerstone in the JavaScript world, setting benchmarks and guiding developers. However, Bun is stepping onto the scene as a noteworthy challenger, pushing boundaries.

While it's still early days for Bun, the buzz it's generating is undeniable. Currently, it's optimized for MacOS and Linux, and while Windows support is in progress, some features are still on the horizon. With all it offers, Bun is certainly a toolkit you should consider exploring.

Visually build with your components

Builder.io is a visual editor that connects to any site or app and lets you drag and drop with your components.

Try it out Learn more

// Dynamically render your components
export function MyPage({ json }) {
  return <BuilderComponent content={json} />
}

registerComponents([MyHero, MyProducts])
Enter fullscreen mode Exit fullscreen mode
Read the full post on the Builder.io blog

Top comments (12)

Collapse
 
dermorzi profile image
DerMorzi

I like the fact, that you mentioned what the differences between JSC and V8 are because so people can see the engines are made for. JSC excellent for serverless and V8 for longtime running applications. This point is often missing in other articles.

One thing I miss here is the fact, that the most internal functionalities from Bun, especially things like Bun.file(), Bun.write() and Bun.serve(), also exist in Deno like Deno.serve(). I know this is not a comparison between Bun and Deno, but this has the potential to make the choice between the Runtimes easier.

Collapse
 
lebinhan profile image
Lê Bình An

I haven't take time to test our every statement in this article yet, but I immediately scroll down to this comment section because I want to say thank you and give you a unicorn for spending your time make such great, detailed and in-depth article. This kind of quality post is rare these day and I really appreciate this.

Another things is I'm still wondering about this opinion I read on an other platform:

 ...It (Bun) ignoring all the safety checks and validation to juice out every drop of performance they can get...
Enter fullscreen mode Exit fullscreen mode

I haven't playing with Bun enough to give any judge, but maybe you will know it better than me, could you try and give some feedback on this?

Collapse
 
ndaidong profile image
Dong Nguyen

Thanks for your article.

There are 2 things that keep me from switching to Bun.

  • cluster mode: this module has not been implemented yet in Bun 1.0
  • logging: winston and pino both don't work well with Bun
Collapse
 
miketalbot profile image
Mike Talbot ⭐

I guess this post covers some concerns about Bun that I feel are worth considering:

For me, the lack of Windows support makes the whole thing a non-starter.

Collapse
 
medzhidov profile image
Ilya Medzhidov

You can use WSL

Collapse
 
pozda profile image
Ivan Pozderac

This is pushing me in the direction to actually make side project with bun.

Lack of Windows support doesn't worry me at all, someone mentioned WSL - which is good alternative. I know there are win servers out there, but most of them are on unix, so really not a show stopper.

I read the comparisons from various sources, but this article actually covers a lot more than just speed comparison.

Thanks for doing the research and sharing its result with us.

Collapse
 
dmondev profile image
dmondev

Hey @Taqui, I just wanted to point out that Node does have a native test runner, since v18, and is stable since v20.
Best

Collapse
 
rjcnd105 profile image
hoejun

I'm glad bun middleware was added to qwik city!

Collapse
 
610470416 profile image
NotFound404

Seems bun will be another deno.
The naming and package system is a mass.
Fast speed is not the only concern for a language or environment.
See no bright future for me.

Collapse
 
yanakanavalik profile image
Yana

Great post, thank you!

Collapse
 
faisalamin001 profile image
Faisal Amin

Great article, thanks

Collapse
 
lewhunt profile image
Lewis Hunt

Thanks for the info, very useful!