콘텐츠로 이동

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 readingBun.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 npxbunx 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.