Bun logoBunv1.3INTERMEDIATE

Bun

Complete reference for Bun — the all-in-one JavaScript/TypeScript runtime, package manager, bundler, and test runner

10 min read
bunjavascripttypescriptruntimepackage-managerbundlertest-runnerservernodejs-alternative
Loading your progress

Installation & Setup

Install Bun and configure your project

Install Bun and scaffold a new project

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

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

# Create new project
bun init

# Upgrade Bun
bun upgrade
💡 Bun runs TypeScript and JSX natively — no transpiler config or tsconfig required
⚡ bun init scaffolds package.json, tsconfig.json, and an index.ts in seconds
📌 Bun is a single binary with zero dependencies — installs in under 5 seconds
🟢 Use bun create to scaffold from community templates like React, Hono, or Elysia
installsetupinit

Configure Bun behavior with the bunfig.toml config file

toml
# bunfig.toml — project-level Bun config

[install]
peer = false              # Don't install peer deps
exact = true              # Use exact versions

[test]
coverage = true           # Enable code coverage
💡 bunfig.toml is optional — Bun works with sensible defaults out of the box
⚡ Use ~/.bunfig.toml for global settings that apply to all projects
📌 Environment variables can be referenced with $VAR_NAME syntax in the config
🟢 install.exact = true pins versions without ^ or ~ — great for reproducible builds
configbunfigtoml

Running Scripts & Files

Execute files, scripts, and packages with bun run and bunx

bun run & bunx

Run files, package.json scripts, and remote packages

bash
# Run a TypeScript/JavaScript file
bun run index.ts
bun index.ts          # "run" is optional

# Run package.json scripts
bun run dev
bun run build

# Execute a package (like npx)
bunx cowsay "Hello!"
bunx --bun vite       # Force Bun runtime
💡 bun --watch restarts the whole process on changes; --hot reloads modules in-place
⚡ bun run is 30x faster than npm run — it skips shell interpretation overhead
📌 bunx --bun forces packages like Vite or Prisma to run on Bun instead of Node
🟢 Bun auto-loads .env files — no dotenv package needed
runbunxscriptswatch

Package Manager

Install, manage, and publish packages with bun install

Install, add, remove, and update packages

bash
# Install all dependencies
bun install

# Add a package
bun add express

# Dev dependency
bun add -d typescript

# Remove a package
bun remove express
💡 bun install is up to 30x faster than npm — uses a global cache and hardlinks
⚡ Since Bun 1.2 the default lockfile is the text-based bun.lock — readable in diffs and Git-friendly
📌 Use --frozen-lockfile in CI to ensure deterministic installs
🟢 Workspaces work like npm/yarn workspaces — just add "workspaces" to package.json
installaddremovepackages

HTTP Server

Build high-performance HTTP servers with Bun.serve()

Create an HTTP server with routes and request handling

typescript
Bun.serve({
  port: 3000,
  routes: {
    "/": new Response("Hello!"),
    "/api/users/:id": (req) => {
      return Response.json({ id: req.params.id });
    },
  },
  fetch(req) {
    return new Response("Not Found", { status: 404 });
  },
});
💡 Bun.serve uses Web Standard Request/Response — no framework-specific APIs to learn
⚡ Routes with method handlers (GET/POST) require Bun v1.2.3+ — use fetch() fallback for older versions
📌 Bun.file() in routes lazily loads files into memory — efficient for static assets
🟢 server.reload() hot-swaps the config without dropping existing connections
serverhttproutesapi

Import .html into routes — Bun bundles JS/CSS automatically (Bun 1.2+)

typescript
// Import HTML files directly — Bun bundles their <script>/<link> deps
import index from './index.html'
import dashboard from './dashboard.html'

Bun.serve({
  port: 3000,
  routes: {
    '/': index,                       // bundled, served, hot-reloaded
    '/dashboard': dashboard,

    // API mixed with HTML in the same server
    '/api/posts': {
      GET: () => Response.json([]),
      POST: async (req) => Response.json(await req.json(), { status: 201 }),
    },

    '/api/posts/:id': (req) =>
      Response.json({ id: req.params.id }),
  },

  development: true,                  // browser-side error overlay + HMR
})
💡 import index from "./index.html" — Bun bundles its JS/CSS/TS into the response
⚡ Per-method handlers (GET/POST/PATCH/DELETE) replace if/else routing chains
📌 development: true enables Bun's browser-side error overlay + HMR
🎯 One server can mix HTML routes, JSON APIs, static files, and WebSocket upgrades
fullstackroutesmodern

WebSockets

Built-in WebSocket server support with pub/sub messaging

Add WebSocket support to Bun.serve with handlers and pub/sub

typescript
Bun.serve({
  fetch(req, server) {
    if (server.upgrade(req)) return; // Upgrade to WS
    return new Response("Not a WS request", { status: 400 });
  },
  websocket: {
    open(ws) { ws.send("Welcome!"); },
    message(ws, msg) { ws.send(`Echo: ${msg}`); },
    close(ws) { console.log("Disconnected"); },
  },
});
💡 Bun WebSockets handle 4x more messages per second than Node.js ws package
⚡ Built-in pub/sub with ws.subscribe/publish — no Redis or external broker needed
📌 Pass per-socket data in server.upgrade() — access it later via ws.data
🟢 WebSocket handlers are defined alongside HTTP routes in the same Bun.serve() call
websocketrealtimepubsub

File I/O

Read, write, and manipulate files with Bun.file() and Bun.write()

Fast file operations with Bun.file() and Bun.write()

typescript
// Read a file
const file = Bun.file("data.json");
const text = await file.text();
const json = await file.json();

// Write a file
await Bun.write("output.txt", "Hello World");
await Bun.write("data.json", JSON.stringify({ ok: true }));
💡 Bun.file() is lazy — it only reads from disk when you call .text(), .json(), etc.
⚡ Bun.write() is 10x faster than Node fs.writeFileSync — uses optimized system calls
📌 Pass a Bun.file() or fetch Response directly to Bun.write() for zero-copy operations
🟢 Use file.writer() for incremental writes like logging — flush() ensures data persists
filereadwriteio

SQLite & Database

Built-in SQLite database with the bun:sqlite module

bun:sqlite

Use the built-in SQLite database for fast local storage

typescript
import { Database } from "bun:sqlite";

const db = new Database("app.db");

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

const insert = db.prepare("INSERT INTO users (name, email) VALUES (?, ?)");
insert.run("John", "john@test.com");

const users = db.query("SELECT * FROM users").all();
💡 bun:sqlite is built into Bun — no npm install needed, 3-6x faster than better-sqlite3
⚡ Use db.transaction() for batch inserts — wraps everything in a single atomic operation
📌 Always use prepared statements with ? or $name parameters to prevent SQL injection
🟢 Enable WAL mode for better performance with concurrent reads and writes
sqlitedatabasesql

Test Runner

Jest-compatible testing with bun test

Write Jest-compatible tests with describe, test, and expect

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

describe("math", () => {
  test("addition", () => {
    expect(2 + 2).toBe(4);
  });

  test("async", async () => {
    const res = await fetch("https://api.example.com");
    expect(res.ok).toBeTrue();
  });
});
💡 bun test is Jest-compatible — import from "bun:test" or keep existing Jest imports
⚡ Bun test runner is 10-40x faster than Jest with zero config for TypeScript
📌 Use test.skip() for tests not ready yet and test.todo() as a placeholder reminder
🟢 Run with bun test --watch to auto-rerun on file changes during development
testjestexpectdescribe

Mock functions, spy on methods, and use snapshot testing

typescript
import { test, expect, mock, spyOn } from "bun:test";

// Mock function
const fn = mock(() => 42);
fn();
expect(fn).toHaveBeenCalledTimes(1);

// Snapshot
test("snapshot", () => {
  expect({ foo: "bar" }).toMatchSnapshot();
});
💡 mock.module() replaces entire modules — useful for isolating units under test
⚡ setSystemTime() mocks Date.now() and new Date() globally — no extra library needed
📌 Run bun test --update-snapshots to regenerate snapshot files after intentional changes
🟢 spyOn tracks calls without changing behavior — use mockRestore() to clean up
mockspysnapshottesting

Bundler

Bundle JavaScript, TypeScript, and CSS with bun build

bun build

Bundle code for browsers and servers

bash
# CLI bundling
bun build ./src/index.ts --outdir ./dist
bun build ./src/index.ts --outdir ./dist --minify
bun build ./src/index.ts --target browser
💡 bun build replaces Webpack/esbuild — native bundling with zero config
⚡ Use --compile to create a single executable binary that runs without Bun installed
📌 --target browser strips Node.js APIs; --target bun optimizes for the Bun runtime
🟢 Code splitting with --splitting creates shared chunks for multi-entry bundles
buildbundlecompile

Use Bun.build() in code with plugins and advanced options

typescript
const result = await Bun.build({
  entrypoints: ["./src/index.ts"],
  outdir: "./dist",
  target: "browser",
  minify: true,
});

if (!result.success) {
  console.error(result.logs);
}
💡 Bun.build() returns artifacts with path, size, and hash — useful for build pipelines
⚡ Plugins use the same API as esbuild plugins — most esbuild plugins work in Bun
📌 Use env: "inline" to replace process.env references with actual values at build time
🟢 Check result.success and result.logs to catch build errors programmatically
buildpluginsapi

Shell & Child Processes

Run shell commands with Bun.$ and spawn child processes

Bun Shell ($)

Run shell commands with tagged template literals

typescript
import { $ } from "bun";

// Run a command
await $`echo "Hello World"`;

// Capture output
const result = await $`ls -la`.text();

// Use variables safely (auto-escaped)
const dir = "/tmp";
await $`ls ${dir}`;
💡 Bun.$ auto-escapes interpolated variables — safe from shell injection by default
⚡ Use .nothrow() to prevent throwing on non-zero exit codes — check exitCode instead
📌 Bun Shell works cross-platform — same syntax on macOS, Linux, and Windows
🟢 Chain .text(), .json(), or .lines() to parse command output in one call
shellcommandspawn

Bun.spawn()

Spawn child processes with fine-grained control

typescript
// Run a command
const proc = Bun.spawn(["echo", "Hello"]);
await proc.exited; // Wait for completion

// Capture stdout
const proc = Bun.spawn(["ls", "-la"], {
  stdout: "pipe",
});
const output = await new Response(proc.stdout).text();
💡 Bun.spawn() takes an array of args — no shell interpretation, safe from injection
⚡ Use Bun.spawnSync() for quick blocking commands like git status
📌 Set stdout: "pipe" to capture output — then read it as a Response stream
🟢 Prefer Bun.$ for simple commands and Bun.spawn() for fine-grained process control
spawnprocesschild

Environment & Utilities

Environment variables, hashing, passwords, and built-in utilities

Access and manage environment variables with auto .env loading

typescript
// Access env vars (auto-loaded from .env)
const dbUrl = Bun.env.DATABASE_URL;
const port = Bun.env.PORT || "3000";

// Also available on process.env
const key = process.env.API_KEY;
💡 Bun auto-loads .env files with no dotenv package — just create the file and go
⚡ Use bun --env-file to load a specific env file for different environments
📌 Both Bun.env and process.env work — Bun.env is slightly faster as it skips Node compat
🟢 import.meta.dir/file/path give you the current file location — no __dirname polyfill needed
envenvironmentdotenv

Built-in hashing and secure password operations

typescript
// Hash a string
const hash = Bun.hash("hello world");

// Hash a password (argon2id by default)
const hashed = await Bun.password.hash("secret123");
const valid = await Bun.password.verify("secret123", hashed);
💡 Bun.password.hash uses argon2id by default — bcrypt is also available via the algorithm option
⚡ Bun.hash is non-cryptographic but extremely fast — use for cache keys and checksums
📌 Use Bun.CryptoHasher for SHA-256/SHA-512 when you need cryptographic guarantees
🟢 Bun.sleep() is a built-in async sleep — no setTimeout wrapper needed
hashpasswordcryptobcrypt

Workers & Concurrency

Run code in parallel with Web Workers and Bun-specific APIs

Web Workers

Run CPU-intensive tasks in parallel threads

typescript
// main.ts
const worker = new Worker("./worker.ts");
worker.postMessage({ data: [1, 2, 3] });
worker.onmessage = (e) => {
  console.log("Result:", e.data);
};

// worker.ts
self.onmessage = (e) => {
  const result = e.data.data.map((n) => n * 2);
  self.postMessage(result);
};
💡 Bun Workers support TypeScript directly — no build step needed for worker files
⚡ Use navigator.hardwareConcurrency to create an optimal number of workers for the CPU
📌 Workers run in separate threads with isolated memory — communicate via postMessage
🟢 Use workers for CPU-intensive tasks (parsing, compression, crypto) to keep the main thread responsive
workersconcurrencythreadsparallel