An Introduction to the Bun JavaScript Runtime

    Craig Buckler
    Share

    A rival JavaScript runtime contender has entered the battle between Node.js and Deno. In this article, we take a first look at Bun, and the reasons it may tempt you away from your current favorite.

    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. The origin of the name is unclear, and the logo doesn’t help! It could relate to the food, fluffy rabbits, “bundle”, or perhaps it’s a short, memorable name and the bun.sh domain was available.

    The Bun logo

    The intention: Bun will become a drop-in replacement for Node.js, Deno, serverless JavaScript, and tools such as webpack, Babel, and Yarn. Rather than running npm start to launch your Node script, you can run bun start and take advantage of Bun’s speed.

    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.

    Underbaked Bun

    Bun is in active development, so the following features are yet to appear:

    • a Deno-like permission model to restrict access to files, the network, child processes, environment variables, OS information, and so on. Adding permission rights later can cause complications (as seen in Node.js 20), so I suspect options will arrive before the version 1.0 release.

    • Tools such as a Read-Eval-Print Loop (REPL), dependency inspector, linter, debugger, code formatter, documentation generator, and a standalone script generator are missing but should appear over time.

    Installing Bun

    Bun has not reached a version 1.0 release, but it’s available as a single binary you can install on Linux, macOS, and Windows WSL with:

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

    Alternatively, you can install it with Node’s package manager:

    npm install -g bun
    

    A native Windows version is coming, although the Windows Subsystem for Linux is often the easier and better-performing option. Alternatively, you can also use Docker to run Bun in a container:

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

    Once installed, you can upgrade the engine with:

    bun upgrade
    

    To uninstall, remove 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:

    1. Deno didn’t support Node’s third-party modules.
    2. Migrating from Node.js to Deno required learning new techniques.
    3. 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.

    Summary: Should You Switch to 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, but it’s new, being actively developed, and yet to reach a version 1.0 milestone. 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, etc. We may eventually reach a point where JavaScript runtimes are mostly interchangeable in the same way browsers are today.