Tech Guides
// runtime_reference

Bun

The all-in-one JavaScript runtime. Bundler, test runner, package manager, and Node.js-compatible runtime — built for speed.

ENGINE: JavaScriptCore WRITTEN IN: Zig + C++ TARGET: macOS / Linux / Windows
01

Quick Reference

The most common Bun commands at a glance. Bun replaces node, npm, npx, and webpack/esbuild in a single binary.

Run Scripts
bun run index.ts bun run dev # package.json script bun --watch index.ts bun --hot server.ts
Package Manager
bun install # install deps bun add express bun add -d typescript bun remove lodash
Execute Packages
bunx create-react-app myapp bunx prisma migrate dev bunx tsc --noEmit
Test & Build
bun test # run tests bun test --watch bun build ./src/index.ts --outdir ./dist
Project Init
bun init # new project bun create elysia myapi bun create next-app
Utilities
bun upgrade # update bun bun pm ls # list packages bun pm cache rm # clear cache
02

Installation

Bun ships as a single binary with no dependencies. One command and you're running.

macOS & Linux

# Install via curl (recommended)
curl -fsSL https://bun.sh/install | bash

# Install specific version
curl -fsSL https://bun.sh/install | bash -s "bun-v1.1.0"

# Via Homebrew
brew install oven-sh/bun/bun

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

Windows

# PowerShell
powershell -c "irm bun.sh/install.ps1 | iex"

# Scoop
scoop install bun

# Windows Subsystem for Linux (WSL) — use the curl method above

Docker

# Official image
docker pull oven/bun

# Dockerfile
FROM oven/bun:latest
WORKDIR /app
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile
COPY . .
CMD ["bun", "run", "start"]

Verify Installation

bun --version    # e.g. 1.2.4
bun --revision   # git SHA of the build
which bun        # ~/.bun/bin/bun
Add ~/.bun/bin to your PATH if the installer didn't do it automatically. The install script appends it to ~/.bashrc, ~/.zshrc, or your shell config.
03

Runtime

Bun natively executes JavaScript, TypeScript, JSX, and TSX files with zero configuration. No tsc, no ts-node, no babel.

Running Files

# Run any JS/TS file directly
bun run app.ts
bun run server.jsx
bun run script.mjs

# Shorthand — "run" is optional for files
bun app.ts

# Run package.json scripts
bun run dev          # runs "dev" script
bun run build        # runs "build" script
bun dev              # also works for scripts

Watch Mode & Hot Reload

# Watch mode — restarts on file changes
bun --watch server.ts

# Hot reload — preserves state (for HTTP servers)
bun --hot server.ts
--watch restarts the entire process on change. --hot reloads modules in place without restarting, preserving global state. Use --hot for long-running servers and --watch for scripts.

Environment Variables

# Bun loads .env files automatically (no dotenv needed)
# Priority: .env.local > .env.development > .env

# Access env vars
const port = Bun.env.PORT;          // Bun-native
const db = process.env.DATABASE_URL; // Node compat

# Run with specific env file
bun --env-file=.env.staging app.ts

TypeScript Config

// Bun reads tsconfig.json but does NOT type-check at runtime.
// It only uses it for path mappings and JSX config.
// For type-checking, run tsc separately:
bunx tsc --noEmit

// Recommended tsconfig.json for Bun projects:
{
  "compilerOptions": {
    "target": "ESNext",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "types": ["bun-types"],
    "strict": true,
    "jsx": "react-jsx"
  }
}

Shebangs

#!/usr/bin/env bun

// Make executable scripts with Bun
console.log("Hello from a Bun script!");

# Then: chmod +x script.ts && ./script.ts
04

Package Manager

Bun's package manager is a drop-in replacement for npm, yarn, and pnpm. It uses a binary lockfile (bun.lockb) and a global module cache for extreme speed.

Installing Dependencies

# Install all dependencies from package.json
bun install

# Frozen lockfile (CI mode — fail if lockfile is outdated)
bun install --frozen-lockfile

# Install without running lifecycle scripts
bun install --ignore-scripts

# Production only (skip devDependencies)
bun install --production

Adding & Removing Packages

# Add a dependency
bun add express
bun add zod@latest

# Dev dependency
bun add -d @types/node typescript

# Global install
bun add -g serve

# Exact version (no caret)
bun add --exact react@18.2.0

# Remove a package
bun remove lodash

Lockfile & Cache

# bun.lockb is a binary lockfile (faster to read/write)
# To inspect it as YAML:
bun bun.lockb

# Generate a yarn-compatible text lockfile
bun install --yarn

# Global cache location
bun pm cache     # shows cache dir
bun pm cache rm  # clear cache

# List installed packages
bun pm ls
bun pm ls --all # include transitive deps

Workspaces

// package.json (monorepo root)
{
  "name": "my-monorepo",
  "workspaces": [
    "packages/*",
    "apps/*"
  ]
}

# Install across all workspaces
bun install

# Add dep to a specific workspace
bun add zod --filter packages/shared

# Run script in a specific workspace
bun run --filter apps/web dev
Bun installs packages up to 30x faster than npm. It uses hardlinks from a global cache instead of copying files, and resolves the dependency tree with a native Zig implementation.
05

Bundler

Bun includes a native bundler inspired by esbuild. It handles JavaScript, TypeScript, JSX, CSS, and static assets.

CLI Usage

# Bundle for browser
bun build ./src/index.ts --outdir ./dist

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

# Single output file
bun build ./src/index.ts --outfile bundle.js

# Bundle for Node.js (keeps node_modules external)
bun build ./src/index.ts --target node --outdir ./dist

# Bundle for Bun runtime
bun build ./src/index.ts --target bun --outdir ./dist

# With source maps
bun build ./src/index.ts --outdir ./dist --sourcemap=external

# Code splitting
bun build ./src/index.ts ./src/worker.ts --outdir ./dist --splitting

JavaScript API

const result = await Bun.build({
  entrypoints: ["./src/index.ts"],
  outdir: "./dist",
  target: "browser",  // "browser" | "node" | "bun"
  minify: true,
  splitting: true,
  sourcemap: "external",
  define: {
    "process.env.NODE_ENV": JSON.stringify("production"),
  },
  external: ["fsevents"],
  naming: "[dir]/[name]-[hash].[ext]",
});

if (!result.success) {
  for (const log of result.logs) {
    console.error(log);
  }
}

// result.outputs is an array of Blob objects
for (const output of result.outputs) {
  console.log(output.path, output.size);
}

Compile to Executable

# Create a standalone executable (single binary)
bun build ./src/cli.ts --compile --outfile myapp

# Cross-compile for different targets
bun build ./src/cli.ts --compile --target=bun-linux-x64 --outfile myapp
bun build ./src/cli.ts --compile --target=bun-darwin-arm64 --outfile myapp

# The resulting binary has the Bun runtime embedded
./myapp  # runs without bun installed
bun build --compile embeds the Bun runtime and your code into a single executable. Great for distributing CLI tools without requiring users to install Bun.
06

HTTP Server

Bun.serve() is a high-performance HTTP server built on web-standard APIs. It handles up to 100k+ requests/second on a single thread.

Basic Server

const server = Bun.serve({
  port: 3000,
  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", ts: Date.now() });
    }

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

console.log(`Listening on ${server.url}`);

Streaming & Static Files

// Serve a file
Bun.serve({
  fetch(req) {
    const path = new URL(req.url).pathname;
    const file = Bun.file(`./public${path}`);
    return new Response(file);
  },
});

// Stream a large response
Bun.serve({
  fetch(req) {
    const stream = new ReadableStream({
      async start(controller) {
        for (let i = 0; i < 10; i++) {
          controller.enqueue(`chunk ${i}\n`);
          await Bun.sleep(100);
        }
        controller.close();
      }
    });
    return new Response(stream);
  },
});

WebSocket Server

Bun.serve({
  fetch(req, server) {
    // Upgrade HTTP to WebSocket
    if (server.upgrade(req)) return;
    return new Response("Upgrade failed", { status: 500 });
  },
  websocket: {
    open(ws) {
      console.log("Client connected");
      ws.subscribe("chat");
    },
    message(ws, message) {
      // Broadcast to all subscribers
      ws.publish("chat", `User: ${message}`);
    },
    close(ws) {
      ws.unsubscribe("chat");
    },
  },
});

TLS / HTTPS

Bun.serve({
  port: 443,
  tls: {
    key: Bun.file("./key.pem"),
    cert: Bun.file("./cert.pem"),
  },
  fetch(req) {
    return new Response("Secure!");
  },
});
Bun.serve() uses the web-standard Request / Response API. Code you write for Bun's server is portable to Cloudflare Workers, Deno Deploy, and other edge runtimes with minimal changes.
07

File I/O

Bun provides its own fast file APIs alongside full Node.js fs compatibility. The Bun.file() API uses lazy I/O for maximum performance.

Reading Files

// Bun.file() returns a BunFile (lazy — doesn't read until consumed)
const file = Bun.file("./data.json");

// Read as different types
const text = await file.text();           // string
const json = await file.json();           // parsed JSON
const buf  = await file.arrayBuffer();    // ArrayBuffer
const blob = await file.blob();           // Blob
const stream = file.stream();             // ReadableStream

// File metadata
console.log(file.size);   // bytes
console.log(file.type);   // MIME type
console.log(file.name);   // file name

Writing Files

// Write string
await Bun.write("output.txt", "Hello, world!");

// Write JSON
await Bun.write("data.json", JSON.stringify({ key: "value" }));

// Write Uint8Array / ArrayBuffer
await Bun.write("binary.dat", new Uint8Array([1, 2, 3]));

// Copy a file (uses sendfile for zero-copy)
await Bun.write("copy.txt", Bun.file("original.txt"));

// Write a Response body to disk
const res = await fetch("https://example.com/image.png");
await Bun.write("image.png", res);

Glob & Directory Scanning

import { Glob } from "bun";

const glob = new Glob("**/*.ts");

// Scan files matching pattern
for await (const path of glob.scan("./src")) {
  console.log(path);
}

// Check if a string matches a glob
glob.match("src/index.ts");  // true

stdin / stdout

// Read from stdin
const input = await Bun.stdin.text();

// Write to stdout efficiently
const writer = Bun.stdout.writer();
writer.write("Hello");
writer.flush();

// Pipe stdin to a file
await Bun.write("input.txt", Bun.stdin);
08

SQLite

Bun embeds SQLite as a first-class citizen. The bun:sqlite module provides synchronous, prepared-statement-based access with no native addons or compilation needed.

Basic Usage

import { Database } from "bun:sqlite";

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

// In-memory database
const mem = new Database(":memory:");

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

Prepared Statements

// Insert with prepared statement
const insert = db.prepare(
  "INSERT INTO users (name, email) VALUES (?, ?)"
);
insert.run("Alice", "alice@example.com");

// Named parameters
const insertNamed = db.prepare(
  "INSERT INTO users (name, email) VALUES ($name, $email)"
);
insertNamed.run({ $name: "Bob", $email: "bob@example.com" });

// Query single row
const user = db.prepare("SELECT * FROM users WHERE id = ?").get(1);
// => { id: 1, name: "Alice", email: "alice@example.com" }

// Query all rows
const all = db.prepare("SELECT * FROM users").all();

// Get column values only
const names = db.prepare("SELECT name FROM users").values();
// => [["Alice"], ["Bob"]]

Transactions

// Transaction wrapper (auto-commits or rolls back)
const insertMany = db.transaction((users) => {
  const stmt = db.prepare(
    "INSERT INTO users (name, email) VALUES (?, ?)"
  );
  for (const u of users) {
    stmt.run(u.name, u.email);
  }
  return users.length;
});

const count = insertMany([
  { name: "Carol", email: "carol@test.com" },
  { name: "Dave", email: "dave@test.com" },
]);
// => 2

WAL Mode & Performance

// Enable WAL mode for concurrent reads
db.exec("PRAGMA journal_mode = WAL");

// Read-only database
const ro = new Database("mydb.sqlite", { readonly: true });

// Close when done
db.close();
Bun's SQLite is up to 4x faster than better-sqlite3 in Node.js. Queries return plain objects by default, and prepared statements are cached automatically.
09

Test Runner

Bun has a built-in Jest-compatible test runner. Write tests using familiar describe, it, and expect syntax with zero config.

Writing Tests

// math.test.ts
import { describe, it, expect, beforeEach } from "bun:test";
import { add, multiply } from "./math";

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

  it("multiplies", () => {
    expect(multiply(4, 5)).toBe(20);
  });

  it("handles async", async () => {
    const res = await fetch("https://example.com");
    expect(res.ok).toBe(true);
  });
});

Running Tests

# Run all test files (*.test.ts, *.test.js, *.spec.ts, etc.)
bun test

# Run specific file
bun test math.test.ts

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

# Watch mode
bun test --watch

# With coverage
bun test --coverage

# Timeout per test (ms)
bun test --timeout 10000

# Bail on first failure
bun test --bail

Matchers & Mocking

// Common matchers
expect(value).toBe(expected);
expect(value).toEqual({ deep: "equal" });
expect(value).toBeTruthy();
expect(value).toBeNull();
expect(value).toContain("substring");
expect(value).toThrow();
expect(value).toMatchSnapshot();

// Mocking
import { mock, spyOn } from "bun:test";

const fn = mock(() => 42);
fn();
expect(fn).toHaveBeenCalled();

// Spy on module
const spy = spyOn(console, "log");
console.log("hello");
expect(spy).toHaveBeenCalledWith("hello");

Lifecycle Hooks

import { beforeAll, afterAll, beforeEach, afterEach } from "bun:test";

beforeAll(() => {
  // runs once before all tests
});

afterAll(() => {
  // runs once after all tests
});

beforeEach(() => {
  // runs before each test
});

afterEach(() => {
  // runs after each test
});
10

Node.js Compatibility

Bun aims to be a drop-in replacement for Node.js. Most Node.js built-in modules and npm packages work out of the box.

Supported Built-in Modules

node:fs
Full support including promises API
node:path
Complete implementation
node:http
Server & client support
node:crypto
Hashing, ciphers, TLS
node:buffer
Full Buffer implementation
node:stream
Readable, Writable, Transform
node:child_process
spawn, exec, fork
node:events
EventEmitter
node:os
OS info & constants
node:url
URL parsing & formatting
node:util
Promisify, inspect, types
node:zlib
Gzip, deflate, brotli

Web APIs

Bun implements many Web APIs natively, just like browsers and edge runtimes:

// fetch (built-in, no polyfill needed)
const res = await fetch("https://api.github.com/zen");
const text = await res.text();

// URL, URLSearchParams
const url = new URL("https://example.com?q=bun");

// TextEncoder / TextDecoder
const encoder = new TextEncoder();
const bytes = encoder.encode("hello");

// crypto.subtle (Web Crypto API)
const hash = await crypto.subtle.digest("SHA-256", bytes);

// FormData, Headers, Request, Response
// ReadableStream, WritableStream
// AbortController, AbortSignal
// setTimeout, setInterval, queueMicrotask
// structuredClone, atob, btoa
// performance.now(), console.*

Bun.spawn() (Shell & Processes)

// Run a command
const proc = Bun.spawn(["echo", "hello"]);
await proc.exited; // wait for exit

// Capture output
const proc2 = Bun.spawn(["ls", "-la"], {
  stdout: "pipe",
});
const output = await new Response(proc2.stdout).text();

// Bun Shell ($) — tagged template for shell commands
import { $ } from "bun";

const result = await $`ls -la | grep ".ts"`;
console.log(result.text());

// Pipe commands
await $`cat file.txt | wc -l`;

Migration from Node.js

Node.js Bun Equivalent Notes
node index.js bun index.js Direct replacement
npm install bun install Uses bun.lockb
npx create-app bunx create-app Cached execution
npm run dev bun run dev Faster script startup
jest / vitest bun test Built-in, Jest-compat
ts-node bun run Native TS execution
dotenv Built-in Auto-loads .env files
node --watch bun --watch Also supports --hot
nodemon bun --watch No extra dependency
Not all Node.js APIs are 100% compatible yet. Some edge cases in node:vm, node:cluster, and native C++ addons (N-API) may behave differently. Check the Bun compatibility page for the latest status.
11

Tips & Tricks

Power-user techniques, performance optimizations, and lesser-known Bun features.

Bun.password

// Hash passwords (argon2id by default)
const hash = await Bun.password.hash("mypassword");

// Verify
const valid = await Bun.password.verify("mypassword", hash);

// Use bcrypt instead
const hash2 = await Bun.password.hash("pass", {
  algorithm: "bcrypt",
  cost: 12,
});

Bun.sleep & Bun.sleepSync

// Async sleep
await Bun.sleep(1000); // 1 second

// Synchronous sleep (blocks the event loop)
Bun.sleepSync(500); // 500ms

Hashing

// Built-in fast hashing
const hash = Bun.hash("hello world");         // Wyhash (fast non-crypto)
const crc = Bun.CryptoHasher.hash("SHA256", "data");

// Streaming hasher
const hasher = new Bun.CryptoHasher("sha256");
hasher.update("hello ");
hasher.update("world");
const digest = hasher.digest("hex");

// Hash a file
const fileHash = Bun.hash(await Bun.file("big.bin").arrayBuffer());

bunfig.toml Configuration

# bunfig.toml — project-level config

[install]
# Use a custom registry
registry = "https://registry.npmjs.org/"

[install.scopes]
# Scoped registry
"@myorg" = "https://npm.myorg.com/"

[test]
# Test runner config
coverage = true
coverageThreshold = { line = 80, function = 80 }

[run]
# Shell for lifecycle scripts
shell = "bash"

Useful Patterns

// Check if running in Bun
const isBun = typeof Bun !== "undefined";

// Bun.version & Bun.revision
console.log(Bun.version);   // "1.2.4"
console.log(Bun.revision);  // git SHA

// Bun.peek() — read a promise's value synchronously if resolved
const p = Promise.resolve(42);
console.log(Bun.peek(p));  // 42

// Bun.deepEquals() — fast deep equality check
Bun.deepEquals({ a: 1 }, { a: 1 }); // true

// Bun.escapeHTML() — fast HTML escaping
Bun.escapeHTML('<script>alert("xss")</script>');

// Bun.nanoseconds() — high-resolution timer
const start = Bun.nanoseconds();
// ... work ...
const elapsed = Bun.nanoseconds() - start;

Macros (Compile-Time Code Execution)

// macros.ts — runs at BUNDLE time, not runtime
export function getVersion() {
  return "1.0.0";
}

// app.ts — import with "with" syntax
import { getVersion } from "./macros.ts" with { type: "macro" };

// At build time, getVersion() is called and the result is inlined.
// The bundled output will contain: const v = "1.0.0"
const v = getVersion();
Macros are powerful for embedding build metadata, reading config files at compile time, or generating code. The macro function runs in an isolated sandbox during bundling and its return value replaces the call site.

Bun vs Node.js Performance

Benchmark Bun Node.js
Startup time ~6ms ~35ms
Package install (clean) ~1s ~15s
HTTP requests/sec ~110k ~45k
SQLite reads/sec ~1.2M ~300k
FFI calls/sec ~200M ~40M

Benchmarks are approximate and depend on hardware. Bun uses JavaScriptCore (Safari's engine) while Node.js uses V8 (Chrome's engine).