The all-in-one JavaScript runtime. Bundler, test runner, package manager, and Node.js-compatible runtime — built for speed.
The most common Bun commands at a glance. Bun replaces node, npm, npx, and webpack/esbuild in a single binary.
bun run index.ts
bun run dev # package.json script
bun --watch index.ts
bun --hot server.ts
bun install # install deps
bun add express
bun add -d typescript
bun remove lodash
bunx create-react-app myapp
bunx prisma migrate dev
bunx tsc --noEmit
bun test # run tests
bun test --watch
bun build ./src/index.ts --outdir ./dist
bun init # new project
bun create elysia myapi
bun create next-app
bun upgrade # update bun
bun pm ls # list packages
bun pm cache rm # clear cache
Bun ships as a single binary with no dependencies. One command and you're running.
# 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
# PowerShell
powershell -c "irm bun.sh/install.ps1 | iex"
# Scoop
scoop install bun
# Windows Subsystem for Linux (WSL) — use the curl method above
# 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"]
bun --version # e.g. 1.2.4
bun --revision # git SHA of the build
which bun # ~/.bun/bin/bun
~/.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.
Bun natively executes JavaScript, TypeScript, JSX, and TSX files with zero configuration. No tsc, no ts-node, no babel.
# 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 — 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.
# 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
// 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"
}
}
#!/usr/bin/env bun
// Make executable scripts with Bun
console.log("Hello from a Bun script!");
# Then: chmod +x script.ts && ./script.ts
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.
# 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
# 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
# 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
// 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 includes a native bundler inspired by esbuild. It handles JavaScript, TypeScript, JSX, CSS, and static assets.
# 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
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);
}
# 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.
Bun.serve() is a high-performance HTTP server built on web-standard APIs. It handles up to 100k+ requests/second on a single thread.
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}`);
// 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);
},
});
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");
},
},
});
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.
Bun provides its own fast file APIs alongside full Node.js fs compatibility. The Bun.file() API uses lazy I/O for maximum performance.
// 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
// 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);
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
// 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);
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.
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
)
`);
// 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"]]
// 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
// 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();
better-sqlite3 in Node.js. Queries return plain objects by default, and prepared statements are cached automatically.
Bun has a built-in Jest-compatible test runner. Write tests using familiar describe, it, and expect syntax with zero config.
// 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);
});
});
# 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
// 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");
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
});
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.
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.*
// 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`;
| 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 |
node:vm, node:cluster, and native C++ addons (N-API) may behave differently. Check the Bun compatibility page for the latest status.
Power-user techniques, performance optimizations, and lesser-known Bun features.
// 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,
});
// Async sleep
await Bun.sleep(1000); // 1 second
// Synchronous sleep (blocks the event loop)
Bun.sleepSync(500); // 500ms
// 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 — 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"
// 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.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();
| 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).