An Introduction to the Bun JavaScript Runtime

    Craig Buckler
    Craig Buckler
    Share

    Bun is a rival JavaScript runtime to Node.js and Deno. In this article, we take look at Bun 1.0, and the reasons it may tempt you away from your current favorite.

    The original version of this article was published in early 2023. Now that Bun version 1.0 has arrived, we review whether this milestone will have any impact on the current JavaScript champions.

    Table of Contents

    The History: Where Bun Fits In with Node and Deno

    Ryan Dahl released Node.js in 2009. It wasn’t the first server-side JavaScript runtime, but Node.js rapidly gained momentum. Version 20 arrived in 2023, and Node.js has the largest development ecosystem, with 3.2 million modules — accounting for almost 500 billion downloads per week (according to npmjs.com).

    In 2020, Ryan Dahl released Deno — a remix of “noDe” — to modernize JavaScript development and address legacy issues with Node.js security, API compatibility, tooling, and module management. Reception has been positive, although Deno is yet to challenge Node’s domination.

    In 2022, Jarred Sumner released Bun following his frustrations with the speed of Node.js when developing a Next.js project.

    Bun uses the JavaScriptCore engine, which powers WebKit browsers such as Safari, rather than the V8 engine used in Node.js, Deno, and Chrome.

    The Bun runtime focuses on performance and developer experience. The aim is to eliminate slowness and complexity without throwing away everything that’s great about JavaScript.

    Bun can evolve faster than Node.js — which must remain (mostly) backward-compatible with the existing JavaScript and npm ecosystem.

    Like Deno, Bun has native support for JavaScript and TypeScript without requiring a third-party transpiler or configuration.

    Bun is becoming a drop-in replacement for Node.js, Deno, serverless runtimes, build, and testing tools. It can replace npm, npx, yarn, TypeScript compilers, dotenv, nodemon, pm2, Webpack, Babel, and Jest, to provide a complete all-in-one toolbox for developing applications on a single platform.

    The original runtime was stable, but thanks to contributions from almost 300 developers, the Bun version 1.0 release arrived in September 2023. This will inevitably tempt more developers to migrate to Bun where they can enjoy the benefits described below.

    What’s with the name “Bun”?

    The origin of the “Bun” name is unclear, and the logo doesn’t help! It could relate to the food, fluffy bunny rabbits, “bundle”, or perhaps it’s a short, memorable name and the bun.sh domain was available.

    The Bun logo

    Tasty Bun Benefits

    Node.js and Deno use Chrome’s V8 JavaScript engine. Bun opts for the JavaScriptCore engine which powers WebKit browsers such as Safari. Bun itself is written in Zig — a low-level programming language with manual memory management and native threading to handle concurrency. The result is a lightweight runtime with a smaller memory footprint, quicker start-up times, and performance which can be four times faster than Node.js and Deno under certain (benchmarking) conditions.

    Like Deno, Bun has native support for both JavaScript and TypeScript without requiring a third-party transpiler or configuration. It also supports .jsx and .tsx files to convert HTML-like markup to native JavaScript. Experimental support for running WebAssembly-compiled .wasm files is available.

    Internally, Bun uses ES Modules, supports top-level await, translates CommonJS, and implements Node’s node_modules resolution algorithm. Bun caches modules in ~/.bun/install/cache/ and uses hardlinks to copy them into a project’s node_modules directory. All projects on your system will therefore reference a single instance of the same library, which reduces diskspace requirements and improves installation performance. (Note that macOS installations retain local versions for speed.)

    Bun supports Node’s package.json, npm equivalent commands, and bunx — a npx-like option to auto-install and run packages in a single command. For example:

    bunx cowsay "Hello, world!"
    

    bun init scaffolds empty projects in the same way as npm init, but you can also template a new project with bun create <template> <destination>, where <template> is an official package, a Github repository, or a local package. For example, to create a new Next.js project:

    bun create next ./myapp
    

    Bun includes a bundler to import all dependencies into a single file and can target Bun, Node.js, and client-side JavaScript. This reduces the need to use tools such as esbuild or Rollup:

    bun build ./index.ts —outdir ./out
    

    Most command-line interface options are available via a JavaScript API, so it’s possible to create sophisticated build scripts without a dedicated task runner. Here’s an identical build to the command above:

    await Bun.build({
      entrypoints: ['./index.ts'],
      outdir: './out',
    })
    

    Bun has a standard test runner like Deno and Node.js 20. Running bun test executes scripts named like this:

    *.test.{js|jsx|ts|tsx}
    *_test.{js|jsx|ts|tsx}
    *.spec.{js|jsx|ts|tsx}
    *_spec.{js|jsx|ts|tsx}
    

    There’s no need for nodemon-like tools, since bun has a —watch flag which restarts scripts or tests when you modify a dependency file. Restarts are so fast that it becomes possible to live-reload on each keystroke. (Whether this is practical and not a distraction is another matter!)

    Live reloading is not pretty! (Warning: flickering content!) View original animated GIF.

    A similar —hot mode is available, where Bun watches for changes and soft reloads modules. All files are re-evaluated, but the global state persists.

    Environment variables contained in project .env files are automatically loaded and parsed, making them available in Bun applications, so there’s no need to use packages such as dotenv.

    As well as its own Bun APIs for networking, file access, child processes, and so on, Bun supports:

    • Web APIs such as fetch, URL, blob, WebSocket, JSON, setTimeout, and events.

    • Node.js compatibility APIs such as console, assert, dns, http, path, stream, and util, as well as globals including __dirname, and __filename. Bun claims that 90% of the most-used APIs are fully implemented, although you should double-check those specific to your project.

    Finally, Bun has a native SQLite3 client — bun:sqlite — which could reduce the number of dependencies required in some projects.

    Installing Bun

    Bun is available as a single binary you can install on Linux, macOS, and Windows WSL using curl:

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

    It can be installed via the Node package manager:

    npm install -g bun
    

    Or via Brew on macOS:

    brew tap oven-sh/bun
    brew install bun
    

    Or via Docker:

    docker pull oven/bun
    docker run --rm --init --ulimit memlock=-1:-1 oven/bun
    

    Once installed, you can upgrade Bun using:

    bun upgrade
    

    Or you can uninstall Bun by removing the ~/.bun binary and cache directory:

    rm -rf ~/.bun
    

    Then update your shell configuration file (.bashrc, .zshrc, or similar) to remove ~/.bun/bin references from the $PATH variable.

    Using Bun

    Bun is reliable if you use it from the start of your project. Speed is better than Node.js, although you’re unlikely to see a significant performance boost unless your app is doing specific intensive tasks such heavy SQLite processing or WebSocket messaging.

    Node.js compatibility is good for smaller, simpler projects, and I successfully launched some scripts using bun start without making changes. More complex applications did fail, with obscure error messages generated deep in the node_modules hierarchy.

    Bun vs Deno vs Node.js

    Deno addressed many of Node’s drawbacks, but developers didn’t necessarily feel compelled to switch:

    • Deno didn’t support Node’s third-party modules.
    • Migrating from Node.js to Deno required learning new techniques.
    • While Deno offered a better development experience, Node.js was good enough.

    Deno has now added Node.js compatibility options. That was the easiest way to get developers to transition to Deno, but in the meantime, Node.js has adopted some of Deno’s features, including ES modules, a native test runner, and a —watch mode.

    Bun has taken a different approach, aiming to be a fast, Node-compatible engine with Deno’s advancements. The signs are promising, but it’s not there yet:

    • Performance is great, but few developers complain about Node.js speed.
    • Compatibility is good, but it will be a challenge to support all Node.js modules in a different JavaScript engine. Can JavaScriptCore keep up with V8 developments with far less investment?
    • Bun has the potential to replace your tooling suite, but it’s yet to offer the full range found in Deno.

    Compatibility of Bun with Node.js

    Node.js compatibility is generally good for smaller, simpler projects. You may be able to launch some scripts using bun start instead of npm start without making any changes.

    Bun supports:

    • built-in Node.js modules and APIs such as fs, path, http, console, assert, and so on
    • global variables and objects such as __dirname and process
    • the Node.js module resolution algorithm to locate files in node_modules

    Bun 1.0 claims to run “virtually any Node.js application in the wild”. I’m yet to be fully convinced; complex applications can fail with obscure error messages generated deep inside your third-party modules.

    ES Module and CommonJS Compatibility

    Bun supports both ESM and CommonJS module systems with top-level await. ESM took several years to arrive in Node.js and the ecosystem is still dominated by CommonJS. With Bun, there’s no need for specific file extensions (.js, .cjs, .mjs) or "type": "module" in package.json. You can use import or require() interchangeably in any file!

    Internally, Bun translates all modules to CommonJS and implements Node’s node_modules resolution algorithm. Whether this works as expected is another matter:

    • ES6 modules are pre-parsed in order to resolve further imports before code is executed. Dynamic imports are possible, but should only be considered as a last resort.
    • CommonJS modules load dependencies on demand while executing the code. Dynamic imports are less problematic.

    Execution order can be critical in some applications and it’s the reason Node.js restricts you to EMS or CommonJS in a single file.

    Web APIs

    Bun has built-in support for Web standard APIs available in browsers, such as fetch, Request, Response, URL, blob, WebSocket, JSON, setTimeout, and ReadableStream. Deno introduced these APIs to its server runtime and it makes web coding considerably more consistent. Node.js is catching up but features such as fetch arrived recently in version 18.

    Bun APIs

    Bun ships with highly-optimized standard APIs for common operations such as file reading, file writing, HTTP serving, SQLite querying, and password hashing.

    WebSockets are supported alongside HTTP without requiring a third-party module such as ws:

    Bun.serve({
      port: 8000,
      fetch(request) {
        return new Response('Hello from the Bun server!');
      },
      websocket: {
        open(ws) { ... },
        message(ws, data) { ... },
        close(ws, code, reason) { ... },
      }
    });
    

    TypeScript and JSX Support

    Like Deno, Bun has a JavaScript transpiler built into the runtime. You can run JavaScript, TypeScript, JSX, or TSX files without third-party dependencies. For example:

    bun index.ts
    bun index.jsx
    bun index.tsx
    

    Package Management

    You can use Bun directly as an npm replacement in any Node.js project. For example:

    bun install
    bun add <package> [--dev|--production|--peer]
    bun remove <package>
    bun update <package>
    

    Bun caches modules in ~/.bun/install/cache/ and uses hardlinks to copy them into a project’s node_modules directory. All projects on your system therefore reference a single instance of the same library. This reduces disk space and improves installation performance by up to a factor of 30.

    Live Reloading

    There’s no need for nodemon-like tools, since the bun executable has a -watch flag to restart scripts or tests when you modify a file.

    A similar —hot mode is available, where Bun watches for changes and soft reloads modules. All files are re-evaluated, but the global state persists.

    Testing

    Bun offers a Jest-compatible bun:test test runner with support for snapshot testing, mocking, and code coverage. For example:

    import { test, expect } from "bun:test";
    
    test('2 + 2', () => {
      expect(2 + 2).toBe(4);
    });
    

    Migration from Jest or Vitest is simple, since imports from @jest/globals or vitest are internally re-mapped to bun:test. It shouldn’t be necessary to make code changes.

    Running bun test executes scripts named:

    *.test.{js|jsx|ts|tsx}
    *_test.{js|jsx|ts|tsx}
    *.spec.{js|jsx|ts|tsx}
    *_spec.{js|jsx|ts|tsx}
    

    Script Bundling

    Bun is a JavaScript and TypeScript bundler and minifier which can target code for the browser, Node.js, and other platforms. It’s inspired by esbuild and provides a compatible plugin API:

    // simple build
    Bun.build({
      entrypoints: ['index.js'],
      outdir: 'build'
    });
    

    Benchmarks illustrate Bun can twice as fast as the Go-compiled esbuild with similar minification savings.

    Unlike esbuild, Bun doesn’t support CSS bundling, but that’s likely to arrive given there’s a universal plugin API…

    Universal Plugin API

    Bun’s plugin API is universal: it works for both the bundler and the runtime. You can define plugins to intercept imports and perform custom loading logic. This example implements the import of .yaml files:

    import { plugin } from "bun";
    
    plugin({
      name: 'YAML',
      async setup(build) {
        const { load } = await import('js-yaml');
        const { readFileSync } = await import('fs');
        build.onLoad({ filter: /\.(yaml|yml)$/ }, (args) => {
          const text = readFileSync(args.path, 'utf8');
          const exports = load(text) as Record<string, any>;
          return { exports, loader: 'object' };
        });
      },
    });
    

    Start-up and Execution Speed

    Using bun run <script> rather than npm run <script> typically launches an application 150ms faster. That may be a small improvement, but is’s 4x faster than Node.js and noticeable when you’re running many commands and build scripts. Performance improvements will be greater when using using TypeScript, because there’s no transpilation step.

    Bun also makes the following Node.js performance claims:

    • 5x faster than npx
    • 10x faster at file reads (using Bun.read())
    • 3x faster at file writes (using Bun.write())
    • 4x faster at serving HTTP requests (using Bun.serve())
    • 4x faster at SQLite queries (using bun:sqlite)
    • 13x faster than Jest when testing
    • 8x faster than Vitest when testing

    For bundling, Bun is:

    • almost twice as fast as esbuild
    • 150x faster than Parcel 2
    • 180x faster than Rollup with Terser
    • 220x faster than Webpack

    You’re unlikely to see such gains across every project, but Bun should improve your development experience.

    Experimental Windows Edition

    A native build of Bun will be available for Windows shortly. It’s highly experimental and only supports the JavaScript runtime without performance optimizations. Features such as the package manager, test runner, and bundler have been disabled until they’re stable.

    For the moment, Windows users can install Bun on the Windows Subsystem for Linux — which remains the best option if you’re doing any heavy JavaScript work.

    Summary: Should You Take a Bite from Bun?

    Bun is an accomplished JavaScript runtime, but Node.js remains the champion for mission-critical projects or legacy applications. You should try running your app using bun start, but the larger your codebase, the less chance it will execute without modification.

    Deno is probably a better option than Bun for new projects, given that it’s more mature and feature-complete.

    Bun is great, and being actively developed, but it’s new. The runtime is stable, but few would bet on its long-term future at this stage. That said, Bun has some interesting ideas which I hope both the Node.js and Deno teams consider adopting (CLI APIs and auto-loaded .env please!)

    On a side note, I like Bun’s name, but it can be difficult to search for resources. ChatGPT makes the bold statement that “There is no widely known JavaScript runtime called ‘Bun’. As far as I am aware, there is no such technology in the JavaScript ecosystem.” This may be because post-2021 data is limited, although certain questions return a Bun response and an apology for the mistake!

    I suspect we’re heading toward an age of isomorphic server-side JavaScript, where module developers attempt to write code that’s compatible with all runtimes: Node.js, Deno, Bun, serverless, edge, embedded, and so on. We may eventually reach a point where JavaScript runtimes are mostly interchangeable in the same way browsers are today.

    The Bun version 1.0 milestone may be technically meaningless given the minor changes from 0.8. The psychological difference is greater: Bun feels more complete and usable. More developers will consider the runtime and toolset for their own projects.

    Deno initially went in its own (good) direction but had to backtrack. It was too radical and too incompatible for many Node.js developers. Switching from Node.js to Deno mid-project still isn’t something you should contemplate without accepting the consequences.

    Bun has offered compatibility and speed from the start — a considerable achievement given it’s using a different JavaScript engine. Whether it achieves something close to 100% Node.js compatibility remains to be seen, but you could consider it as a drop-in replacement for some of your toolset on legacy projects.

    Bun’s speed claims are impressive, but few complain about raw Node.js performance, especially when it improves with every release. Some frameworks are slow, but that’s often owing to bloat rather than an inherent fault of the runtime.

    For the moment, Node.js remains the undisputed JavaScript champion. Few will get fired for choosing Node.js, but Bun has avoided some of Deno’s missteps and is rapidly becoming an attractive option.

    Frequently Asked Questions about Bun, the JavaScript runtime

    What is Bun, and what does it do?

    Bun is a JavaScript runtime that allows you to execute JavaScript code outside of a web browser or Node.js environment. It provides a lightweight and fast runtime for running JavaScript applications and is particularly well-suited for serverless and cloud-native environments.

    How does Bun differ from Node.js?

    Bun is designed to be a lightweight and serverless-friendly runtime for JavaScript. It is more minimalistic and optimized for quick startup times, making it a good choice for serverless functions and microservices. In contrast, Node.js is a more comprehensive runtime that includes a larger standard library.

    What are the main use cases for Bun?

    Bun is particularly well-suited for serverless computing, cloud functions, and microservices. It excels in scenarios where fast startup times and minimal resource usage are critical, such as handling short-lived, event-driven tasks in the cloud.