Tools

Bun: The Fast JavaScript Runtime

Mayur Dabhi
Mayur Dabhi
June 11, 2026
14 min read

JavaScript has always had a speed problem — not in the browser, but in the tooling. Installing packages, running tests, bundling assets, and spinning up a dev server have traditionally meant juggling multiple tools each with their own startup overhead. Bun is a radical answer to that problem: a single, blazing-fast runtime that replaces Node.js, npm, Webpack, and Jest in one install. Built from scratch in Zig and powered by Apple's JavaScriptCore engine, Bun can install packages up to 30× faster than npm and execute scripts significantly faster than Node.js. In this guide you'll learn exactly what Bun is, how to set it up, and when it's the right choice for your next project.

What Makes Bun So Fast?

Bun uses Apple's JavaScriptCore engine (the same engine that powers Safari) instead of V8. JavaScriptCore has faster cold-start times. Bun is also written in Zig, a systems language that compiles to highly optimized native code with minimal overhead — no GC pauses, no abstractions layered on abstractions.

What Is Bun?

Bun is not just another JavaScript runtime. It is an all-in-one toolkit that ships four capabilities in a single binary:

Created by Jarred Sumner and released as stable (v1.0) in September 2023, Bun is fully open-source under the MIT license and has quickly become one of the most-starred JavaScript tools on GitHub.

bun Single Binary (Zig) JavaScriptCore Apple's JS Engine Runtime JS & TS execution Web APIs built-in Package Manager bun install / add npm compatible Bundler ESM / CJS / CSS Tree shaking Test Runner Jest-compatible API bun test Bun Architecture: One Binary, Four Tools

Bun bundles runtime, package manager, bundler, and test runner into a single binary

Installation and Setup

Installing Bun is a single command on macOS, Linux, or WSL. There is no installer wizard, no PATH fiddling — the install script puts a single binary in ~/.bun/bin/bun and adds it to your shell profile.

1

Install Bun

Run the official install script. It works on macOS, Linux, and Windows via WSL.

Terminal — Install Bun
# macOS / Linux / WSL (recommended)
curl -fsSL https://bun.sh/install | bash

# macOS with Homebrew
brew install oven-sh/bun/bun

# npm (if you already have Node.js)
npm install -g bun

# Verify installation
bun --version
# e.g. 1.1.34
2

Create a new project

Use bun init to scaffold a new project interactively, or bun create to use a template.

Terminal — Create a project
# Interactive project init
bun init

# Create from a template
bun create react my-app       # React + Vite
bun create next my-app        # Next.js
bun create hono my-api        # Hono web framework
bun create elysia my-api      # Elysia (Bun-first framework)

# Change directory and run
cd my-app
bun run dev
3

Run any JavaScript or TypeScript file

Bun runs .js, .ts, .jsx, and .tsx files natively — no tsc, no Babel, no ts-node needed.

Terminal — Running files
# Run a TypeScript file directly
bun run index.ts

# Watch mode (re-run on file changes)
bun --watch run index.ts

# Execute a one-liner
bun -e "console.log(Bun.version)"
Windows Support

Native Windows support (without WSL) became stable in Bun v1.1. You can install it with powershell -c "irm bun.sh/install.ps1 | iex" on Windows 10/11.

Bun as a Package Manager

One of Bun's most immediate wins over Node.js is its package manager. bun install reads your existing package.json and downloads packages from the npm registry — but it caches every package globally in a binary format that makes reinstalls nearly instantaneous.

Key Package Manager Commands

# Install all dependencies from package.json
bun install

# Install without devDependencies (production)
bun install --production

# Install frozen (fails if bun.lockb would change)
bun install --frozen-lockfile

# Force re-download (ignore cache)
bun install --force
# Add a dependency
bun add express
bun add zod

# Add a dev dependency
bun add -d typescript @types/express

# Add a peer dependency
bun add --peer react

# Remove a package
bun remove lodash

# Upgrade all packages
bun update
# Run a script from package.json
bun run build
bun run test
bun run dev

# Shorthand — if no ambiguity with built-in commands
bun dev
bun build

# Run a local binary from node_modules
bun x tsc --version          # like npx but faster
bunx eslint src/              # alias for bun x
# package.json (root of a monorepo)
{
  "name": "my-monorepo",
  "workspaces": ["packages/*", "apps/*"]
}

# Install all workspace dependencies at once
bun install

# Run a script in a specific workspace
bun run --filter="@myapp/ui" build

# Run a script in all workspaces
bun run --filter="*" test

Lockfile: bun.lockb

Bun uses a binary lockfile (bun.lockb) instead of the text-based package-lock.json. It is faster to parse and smaller on disk. Commit it to your repository just like any other lockfile. To inspect it as text, run:

Terminal
# Print lockfile as human-readable YAML
bun bun.lockb

Building HTTP Servers with Bun

Bun ships a high-performance HTTP server via Bun.serve(). It is up to 4× faster than Node.js's http module in benchmarks and supports WebSockets natively. Here is a minimal REST API:

server.ts — HTTP server with Bun.serve()
const PORT = process.env.PORT ? Number(process.env.PORT) : 3000;

const server = Bun.serve({
  port: PORT,
  async fetch(req) {
    const url = new URL(req.url);

    // GET /
    if (url.pathname === "/" && req.method === "GET") {
      return Response.json({ message: "Hello from Bun!", version: Bun.version });
    }

    // GET /users
    if (url.pathname === "/users" && req.method === "GET") {
      const users = [
        { id: 1, name: "Alice" },
        { id: 2, name: "Bob" },
      ];
      return Response.json(users);
    }

    // POST /users
    if (url.pathname === "/users" && req.method === "POST") {
      const body = await req.json();
      // ... save to database
      return Response.json({ created: true, user: body }, { status: 201 });
    }

    return new Response("Not Found", { status: 404 });
  },
});

console.log(`Listening on http://localhost:${server.port}`);
Terminal
bun run server.ts
# Listening on http://localhost:3000

WebSocket Support

Bun's server also supports WebSockets with zero extra dependencies. Add a websocket handler alongside fetch:

ws-server.ts — WebSockets with Bun.serve()
Bun.serve({
  port: 3000,
  fetch(req, server) {
    // Upgrade HTTP request to WebSocket
    if (server.upgrade(req)) return; // upgrade succeeded
    return new Response("Use WebSocket", { status: 426 });
  },
  websocket: {
    open(ws) {
      console.log("Client connected");
      ws.send("Welcome!");
    },
    message(ws, data) {
      console.log("Received:", data);
      ws.send(`Echo: ${data}`);
    },
    close(ws) {
      console.log("Client disconnected");
    },
  },
});

Bun's Built-in Test Runner

Bun ships a Jest-compatible test runner. You can migrate most Jest suites to Bun with zero or minimal changes — the API (describe, it, expect, beforeEach, afterAll, mocks) is intentionally identical.

math.test.ts — Writing tests with Bun
import { describe, it, expect, beforeEach, mock } from "bun:test";

// Simple utility being tested
function add(a: number, b: number) {
  return a + b;
}

describe("add()", () => {
  it("adds two positive numbers", () => {
    expect(add(2, 3)).toBe(5);
  });

  it("handles negative numbers", () => {
    expect(add(-1, 1)).toBe(0);
  });

  it("returns the correct type", () => {
    expect(typeof add(1, 2)).toBe("number");
  });
});

// Mocking
const fetchData = mock(async (id: number) => {
  return { id, name: "Mocked User" };
});

describe("fetchData()", () => {
  it("calls the mock once", async () => {
    const result = await fetchData(1);
    expect(fetchData).toHaveBeenCalledTimes(1);
    expect(result.name).toBe("Mocked User");
  });
});
Terminal — Running tests
# Run all test files
bun test

# Watch mode
bun test --watch

# Run a specific file
bun test math.test.ts

# Run tests matching a pattern
bun test --test-name-pattern "adds two"

# Code coverage
bun test --coverage
Migrating from Jest

Change import { describe, it, expect } from "@jest/globals" or jest to "bun:test" and remove the jest and ts-jest dependencies. Most suites work without any other changes.

Bun's Bundler

The bun build command bundles JavaScript and TypeScript for browsers or other targets. It handles ESM, CommonJS, CSS, and static assets. While it is not as feature-rich as Webpack or Rollup for complex plugin ecosystems, its speed makes it ideal for fast build pipelines and server-side bundling.

Terminal — Bundling a project
# Bundle for browser (default target)
bun build ./src/index.ts --outdir ./dist

# Minified production build
bun build ./src/index.ts --outdir ./dist --minify

# Bundle as CommonJS module
bun build ./src/lib.ts --outdir ./dist --format cjs

# Bundle with sourcemaps
bun build ./src/index.ts --outdir ./dist --sourcemap external

# Bundle everything into a single file
bun build ./src/index.ts --outfile ./dist/bundle.js

# Target specific environments
bun build ./src/index.ts --target browser   # Browser (default)
bun build ./src/index.ts --target bun       # Bun runtime
bun build ./src/index.ts --target node      # Node.js

Using the Bundler API

You can also drive the bundler programmatically from a build script:

build.ts — Programmatic bundling
const result = await Bun.build({
  entrypoints: ["./src/index.ts"],
  outdir: "./dist",
  minify: true,
  sourcemap: "external",
  target: "browser",
  define: {
    "process.env.NODE_ENV": '"production"',
  },
  plugins: [
    // Bun has a plugin API compatible with esbuild plugins
  ],
});

if (!result.success) {
  console.error("Build failed:", result.logs);
  process.exit(1);
}

console.log(`Built ${result.outputs.length} files`);

TypeScript and JSX — Zero Config

Bun treats TypeScript as a first-class citizen. There is no need to install typescript, configure tsconfig.json for execution, or run tsc first. Bun strips types at runtime using its own transpiler.

Type Checking

Bun executes TypeScript by stripping types — it does not type-check your code. For type checking, still run bun x tsc --noEmit in your CI pipeline. Think of Bun as a replacement for ts-node, not for the TypeScript compiler.

app.tsx — JSX with Bun (React)
// Bun reads jsxImportSource from tsconfig.json automatically
// No Babel, no extra config needed

import { renderToString } from "react-dom/server";

function App() {
  return (
    <html>
      <head><title>My App</title></head>
      <body>
        <h1>Hello from Bun + React SSR!</h1>
      </body>
    </html>
  );
}

const html = renderToString(<App />);

Bun.serve({
  port: 3000,
  fetch() {
    return new Response(html, {
      headers: { "Content-Type": "text/html" },
    });
  },
});

Bun vs Node.js: Performance Comparison

Numbers vary by workload, but these benchmarks represent typical results from the official Bun documentation and community testing:

Benchmark Node.js Bun Speedup
bun install vs npm install (cold) ~8s ~0.7s ~11×
bun install vs npm install (cached) ~2s ~0.1s ~20×
Script startup time (hello.ts) ~120ms ~10ms ~12×
HTTP requests/sec (hello world) ~80k req/s ~250k req/s ~3×
Running a test suite (100 tests) ~3.2s (Jest) ~0.4s (bun test) ~8×
Bundling a medium SPA ~4s (Webpack) ~0.3s (bun build) ~13×
Package Install Time (smaller = faster) npm 8.0s yarn 5.2s pnpm 3.7s bun 0.7s

Cold install of a typical project with ~200 packages

Node.js Compatibility

Bun aims for full Node.js API compatibility. The vast majority of npm packages and Node.js code works as-is. Bun implements:

Bun's Built-in Globals (No Imports Needed)

Global Description
Bun.file(path) Open a file as a lazy blob — very fast I/O
Bun.write(path, data) Write a string, Buffer, or blob to disk
Bun.serve(options) Start an HTTP/WebSocket server
Bun.build(options) Bundle files programmatically
Bun.hash(data) Super-fast non-cryptographic hash (Wyhash)
Bun.password.hash(pw) Hash a password with Argon2 or bcrypt
Bun.sql Built-in PostgreSQL client (Bun v1.1+)
Bun.sqlite Built-in SQLite driver — zero dependencies
Bun.spawn() Run child processes with streaming I/O
Bun.env Alias for process.env with .env auto-loaded

Built-in SQLite — Zero Dependencies

One of Bun's most useful built-in features for backend developers is bun:sqlite — a fast, synchronous SQLite client with no npm install required. It uses the same API shape as better-sqlite3:

db.ts — Built-in SQLite with Bun
import { Database } from "bun:sqlite";

// Open or create a database file
const db = new Database("myapp.db");

// Create a table
db.run(`
  CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    email TEXT UNIQUE NOT NULL,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
  )
`);

// Prepared statement (reusable, prevents SQL injection)
const insertUser = db.prepare(
  "INSERT INTO users (name, email) VALUES ($name, $email)"
);

// Insert a user
insertUser.run({ $name: "Alice", $email: "alice@example.com" });
insertUser.run({ $name: "Bob",   $email: "bob@example.com" });

// Query all users
const allUsers = db.query("SELECT * FROM users").all();
console.log(allUsers);
// [{ id: 1, name: 'Alice', email: 'alice@example.com', ... }, ...]

// Query a single user
const getById = db.prepare("SELECT * FROM users WHERE id = $id");
const user = getById.get({ $id: 1 });
console.log(user?.name); // "Alice"

db.close();

When to Use Bun vs Node.js

Bun is not a drop-in replacement for every Node.js use case today — certain native addons (.node files), some Webpack plugins, and a small number of npm packages that use Node.js-specific internals may not work. Here is a practical guide:

Scenario Recommended Reason
New greenfield project Bun Faster DX from day one, native TS, built-in test runner
CLI tools and scripts Bun Instant startup (~10ms vs ~120ms), no install step needed
High-throughput API server Bun Bun.serve() handles more req/s with less memory
Monorepo with npm workspaces Bun Dramatically faster installs in CI
Project with native addons (.node) Node.js Bun does not yet support native Node addons (napi is partial)
Legacy Express + complex middleware stack Node.js Some edge cases in middleware that rely on Node internals
Next.js / Nuxt (full-stack framework) Both Use Bun as package manager; runtime support improves each release

Conclusion and Key Takeaways

Bun represents the most significant shift in the JavaScript tooling landscape since npm was created. By collapsing four separate tools into a single, hand-optimized binary, it eliminates a huge source of incidental complexity for JavaScript developers. Whether you adopt it for package management alone or commit to it as your full runtime, the productivity gains are real and measurable.

Key Takeaways

  • All-in-one: Runtime, package manager, bundler, and test runner in one binary
  • Speed: 10–30× faster installs; 3–12× faster scripts and tests than Node.js equivalents
  • TypeScript first-class: Run .ts files directly, no build step for development
  • Node.js compatible: Most npm packages and Node.js APIs work without changes
  • Built-in batteries: SQLite, password hashing, HTTP server, WebSockets — no extra installs
  • Excellent for scripts and CLIs: Near-instant cold start makes Bun ideal for dev tooling
  • Not yet universal: Native addons and some complex Webpack ecosystems still require Node.js
"The goal of Bun is to run most of the world's JavaScript outside of browsers, faster."
— Jarred Sumner, Creator of Bun

The JavaScript ecosystem is rarely still, and Bun is one of the most exciting things to happen to it in years. Try swapping npm install for bun install in your next project — you'll feel the difference immediately, and you may never look back.

Bun JavaScript Runtime Performance TypeScript Package Manager Node.js
Mayur Dabhi

Mayur Dabhi

Full Stack Developer with 5+ years of experience building scalable web applications with Laravel, React, and Node.js.