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
| Command | Description |
|---|---|
bun run file.ts | Run a TypeScript/JavaScript file |
bun run --watch file.ts | Run with hot-reload on file changes |
bun run dev | Run the “dev” script from package.json |
bun install | Install all dependencies |
bun install --frozen-lockfile | Install without updating lockfile (CI) |
bun add package | Add a dependency |
bun add -d package | Add a dev dependency |
bun add -g package | Install a global package |
bun remove package | Remove a dependency |
bun update | Update all packages to latest |
bun update package | Update a specific package |
bun link | Link a local package globally |
bun pm ls | List installed packages |
bun pm cache rm | Clear the package cache |
bun build src/index.ts | Bundle for production |
bun test | Run all tests |
bun test --watch | Run tests in watch mode |
bun test --coverage | Run tests with coverage |
bun repl | Start interactive REPL |
bunx package | Execute a package binary (like npx) |
bun x package | Alias for bunx |
bun init | Initialize a new project |
bun create template | Scaffold 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.