Skip to content

Bun

Bun is an all-in-one JavaScript toolkit built from scratch with Zig and JavaScriptCore. It is dramatically faster than Node.js and npm for most operations — startup time, package installs, bundling, and test execution. Bun is a drop-in replacement for Node.js with full npm compatibility.

Installation

# macOS and Linux
curl -fsSL https://bun.sh/install | bash

# macOS (Homebrew)
brew install oven-sh/bun/bun

# Windows (PowerShell — native support since Bun 1.1)
powershell -c "irm bun.sh/install.ps1 | iex"

# npm (for bootstrapping)
npm install -g bun

# Upgrade Bun
bun upgrade

# Upgrade to a specific version
bun upgrade --version 1.1.30

# Check version
bun --version

Configuration

Bun reads bunfig.toml for runtime and package manager configuration:

# bunfig.toml

[install]
# Use a custom registry
registry = "https://registry.npmjs.org"
# Frozen lockfile in CI
frozenLockfile = false
# Save exact versions (no ^ or ~)
exact = false

[install.cache]
# Custom cache directory
dir = "~/.bun/install/cache"
# Disable cache (useful for debugging)
disable = false

[test]
# Root directory for tests
root = "."
# Test file pattern
preload = ["./tests/setup.ts"]
timeout = 5000
coverage = false

[run]
# Automatically install missing packages
autoInstall = true
# Shell to use for bun run scripts
shell = "bun"

[bundle]
# Public path for assets
publicPath = "/"

package.json Integration

{
  "name": "my-app",
  "version": "1.0.0",
  "scripts": {
    "dev": "bun run --watch src/index.ts",
    "build": "bun build src/index.ts --outdir dist",
    "test": "bun test",
    "start": "bun run dist/index.js"
  },
  "dependencies": {
    "hono": "^4.0.0",
    "zod": "^3.22.0"
  },
  "devDependencies": {
    "@types/bun": "latest"
  }
}

Core Commands

CommandDescription
bun run file.tsRun a TypeScript/JavaScript file
bun run --watch file.tsRun with hot-reload on file changes
bun run devRun the “dev” script from package.json
bun installInstall all dependencies
bun install --frozen-lockfileInstall without updating lockfile (CI)
bun add packageAdd a dependency
bun add -d packageAdd a dev dependency
bun add -g packageInstall a global package
bun remove packageRemove a dependency
bun updateUpdate all packages to latest
bun update packageUpdate a specific package
bun linkLink a local package globally
bun pm lsList installed packages
bun pm cache rmClear the package cache
bun build src/index.tsBundle for production
bun testRun all tests
bun test --watchRun tests in watch mode
bun test --coverageRun tests with coverage
bun replStart interactive REPL
bunx packageExecute a package binary (like npx)
bun x packageAlias for bunx
bun initInitialize a new project
bun create templateScaffold from a template

Advanced Usage

HTTP Server

// Bun's native HTTP server (fastest option)
const server = Bun.serve({
  port: 3000,
  async fetch(req) {
    const url = new URL(req.url);
    if (url.pathname === "/") {
      return new Response("Hello from Bun!");
    }
    if (url.pathname === "/json") {
      return Response.json({ message: "Hello", timestamp: Date.now() });
    }
    return new Response("Not Found", { status: 404 });
  },
  error(err) {
    return new Response(`Error: ${err.message}`, { status: 500 });
  },
});

console.log(`Listening on http://localhost:${server.port}`);

WebSocket Server

const server = Bun.serve({
  port: 3001,
  fetch(req, server) {
    if (server.upgrade(req)) {
      return; // WebSocket upgraded successfully
    }
    return new Response("Use WebSocket", { status: 426 });
  },
  websocket: {
    open(ws) {
      console.log("Client connected");
      ws.subscribe("room1"); // pub/sub channel
    },
    message(ws, message) {
      ws.publish("room1", message); // broadcast to channel
    },
    close(ws, code, reason) {
      console.log("Client disconnected", code);
    },
  },
});

SQLite (Built-in)

import { Database } from "bun:sqlite";

// Open or create a database
const db = new Database("myapp.db", { create: true });

// Create 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 statements (recommended)
const insertUser = db.prepare("INSERT INTO users (name, email) VALUES (?, ?)");
insertUser.run("Alice", "alice@example.com");

// Query with type safety
interface User {
  id: number;
  name: string;
  email: string;
}
const getUser = db.query<User, [number]>("SELECT * FROM users WHERE id = ?");
const user = getUser.get(1);

// Batch insert with transaction
const insertMany = db.transaction((users: Array<[string, string]>) => {
  for (const [name, email] of users) {
    insertUser.run(name, email);
  }
});
insertMany([["Bob", "bob@example.com"], ["Carol", "carol@example.com"]]);

Bundler

# Bundle entry point
bun build src/index.ts --outdir dist

# Bundle as ESM module
bun build src/lib.ts --outdir dist --format esm

# Bundle for browser (minified)
bun build src/app.ts --outdir dist --minify --target browser

# Bundle with source maps
bun build src/app.ts --outdir dist --sourcemap=external

# Watch mode for bundling
bun build src/app.ts --outdir dist --watch

# Bundle a CSS file
bun build styles/main.css --outdir dist

Macros (Compile-time Code)

// macro.ts — runs at bundle time
export function getEnv(key: string): string {
  return process.env[key] ?? "";
}

// index.ts
import { getEnv } from "./macro.ts" with { type: "macro" };

// At bundle time, this becomes a string literal:
const apiUrl = getEnv("API_URL");

FFI (Foreign Function Interface)

import { dlopen, FFIType, suffix } from "bun:ffi";

// Load a native library
const lib = dlopen(`libmylib.${suffix}`, {
  add: {
    args: [FFIType.i32, FFIType.i32],
    returns: FFIType.i32,
  },
});

const result = lib.symbols.add(40, 2);
console.log(result); // 42
lib.close();

Testing

import { expect, test, describe, beforeEach, mock } from "bun:test";

describe("Math utilities", () => {
  test("adds numbers", () => {
    expect(1 + 1).toBe(2);
  });

  test("async operation", async () => {
    const result = await fetch("https://api.example.com");
    expect(result.status).toBe(200);
  });
});

// Mocking
const mockFetch = mock(() =>
  Promise.resolve(new Response(JSON.stringify({ ok: true })))
);
global.fetch = mockFetch;

test("uses mock", async () => {
  await fetch("https://example.com");
  expect(mockFetch).toHaveBeenCalledTimes(1);
});

// Snapshots
test("snapshot", () => {
  const data = { name: "Bun", fast: true };
  expect(data).toMatchSnapshot();
});

Common Workflows

Starting a New Project

# Initialize interactively
bun init

# Use a template
bun create hono my-api
bun create react my-app
bun create next my-nextapp
bun create vite my-vite-app

# From a GitHub template
bun create github-user/template-repo my-project

Speed Benchmark Comparison

# Package install speed
time npm install      # ~30s for a medium project
time bun install      # ~3s for the same project

# Script startup time
time node -e "console.log('hello')"   # ~80ms
time bun -e "console.log('hello')"    # ~5ms

Running Node.js Projects

# Drop-in replacement — works with existing package.json
bun install          # reads package.json, generates bun.lockb
bun run dev          # runs the dev script
bun run build        # runs the build script

# Run any Node.js file
bun run server.js
bun run index.mjs

CI Setup (GitHub Actions)

name: CI
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: oven-sh/setup-bun@v2
        with:
          bun-version: latest
      - run: bun install --frozen-lockfile
      - run: bun test --coverage
      - run: bun build src/index.ts --outdir dist

Tips and Best Practices

Commit bun.lockb — The binary lockfile bun.lockb ensures reproducible installs. Always commit it to version control. Use bun install --frozen-lockfile in CI to prevent accidental updates.

Use bun:sqlite for lightweight persistence — Bun’s built-in SQLite module requires no dependencies and is significantly faster than the better-sqlite3 npm package for read-heavy workloads.

Prefer Bun.serve() over Express — For new projects, Bun.serve() is 2–4x faster than Express on Node.js. Consider Hono (npm) for a framework with routing on top of Bun’s native server.

Use Bun.file() for efficient file reading — Bun.file("path") returns a lazy File object. Use .text(), .json(), or .arrayBuffer() to read. It’s faster than fs.readFile.

const file = Bun.file("data.json");
const data = await file.json();
const size = file.size; // byte size without reading

Enable auto-install — Set autoInstall = true in bunfig.toml to automatically install missing packages on bun run. Useful during development to avoid constant bun add calls.

Use bunx instead of npx — bunx downloads and executes packages without the startup overhead of npx. It caches binaries locally for subsequent calls.

TypeScript works natively — Bun transpiles TypeScript without configuration. You don’t need ts-node, tsx, or tsc to run .ts files — just run them directly with bun run.

Watch mode is built-in — Use bun run --watch file.ts for development instead of nodemon. It’s faster and doesn’t require an additional package.