Tip
Bun is your all-in-one JavaScript toolkit: runtime, package manager, bundler, and test runner in a single binary. If you're coming from Node.js, drop Bun in and gain dramatically faster installs and startup times with zero config changes.
1. Quick Start
Bun is a fast, all-in-one JavaScript runtime built with performance as a first principle. It runs JavaScript, TypeScript, JSX, and TSX natively without any additional tooling. The package manager installs dependencies up to 25× faster than npm. The bundler replaces webpack and esbuild. The test runner is Jest-compatible. SQLite, PostgreSQL, MySQL, Redis, and S3 clients ship built-in. Shell scripting works via Bun.$. And bun build --compile produces self-contained executables.
Prerequisites:
- macOS (arm64 or x64), Linux (arm64 or x64, glibc or musl), or Windows (x64 or arm64)
- No Node.js required — Bun is self-contained
Install in one line:
curl -fsSL https://bun.sh/install | bash
Your first project:
mkdir my-app && cd my-app
bun init
bun run index.ts
bun init scaffolds a minimal TypeScript project with package.json, tsconfig.json, index.ts, .gitignore, and README.md. bun run index.ts executes it immediately — no compile step, no ts-node, no separate transpiler.
Expected output from bun init:
bun init helps you get started with a minimal project and tries to
guess sensible defaults. Press ^C anytime to quit.
package name (my-app):
entry point (index.ts):
Done! A package.json file was saved in the current directory.
+ index.ts
+ .gitignore
+ tsconfig.json (for editor auto-complete)
+ README.md
To get started, run:
bun run index.ts
Verify your installation:
bun --version
# 1.3.13
Note
Bun reads
tsconfig.jsonfor path aliases and JSX settings automatically. You don't need any additional TypeScript configuration to run.tsfiles.
2. Installation
Supported Platforms
| Platform | Architecture | Notes |
|---|---|---|
| macOS | arm64 (Apple Silicon) | Primary development platform |
| macOS | x64 (Intel) | Fully supported |
| Linux | arm64 | glibc and musl |
| Linux | x64 | glibc and musl |
| Windows | x64 | Full support |
| Windows | arm64 | Full support |
Installation Methods
curl (macOS and Linux — recommended)
curl -fsSL https://bun.sh/install | bash
After install, restart your terminal or run source ~/.bashrc (or ~/.zshrc). The installer adds Bun to ~/.bun/bin and updates your shell's PATH.
npm
npm install -g bun
Useful if you already have Node.js and want a quick install without running a shell script.
Homebrew (macOS and Linux)
brew tap oven-sh/bun
brew install oven-sh/bun/bun
Docker
docker pull oven/bun:latest
Available image variants:
| Tag | Base | Use Case |
|---|---|---|
oven/bun:latest |
Debian | General purpose |
oven/bun:alpine |
Alpine | Minimal image size |
oven/bun:debian |
Debian | Explicit Debian |
oven/bun:debian-slim |
Debian Slim | Smaller Debian footprint |
oven/bun:distroless |
Distroless | Production security hardening |
Example Dockerfile:
FROM oven/bun:latest
WORKDIR /app
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile
COPY . .
CMD ["bun", "run", "src/index.ts"]
Scoop (Windows)
scoop install bun
Proto (version manager)
proto install bun
CPUs Without AVX2 Support
Older CPUs that lack AVX2 instructions require the baseline build:
curl -fsSL https://bun.sh/install | bash -s -- --no-avx2
Important
If Bun crashes immediately with an illegal instruction error on startup, your CPU likely lacks AVX2 support. Reinstall using the
--no-avx2flag above.
Upgrading Bun
bun upgrade
This downloads the latest release directly from GitHub. To upgrade to a specific version:
bun upgrade --version 1.3.13
To upgrade to the canary (nightly) build:
bun upgrade --canary
3. Configuration
bunfig.toml
bunfig.toml is Bun's primary configuration file. It can live at the project root (per-project) or at ~/.bunfig.toml (global user defaults). Project-level settings override global ones.
[install]
# Do not install devDependencies
production = false
# Fail if bun.lockb would change (useful in CI)
frozen-lockfile = false
# Default registry
registry = "https://registry.npmjs.org/"
# Default installation backend
# Options: clonefile (default on macOS), hardlink, symlink, copyfile
# backend = "clonefile"
[run]
# Shell used to run package.json scripts
# "bun" = Bun's built-in shell (cross-platform)
# "system" = /bin/sh on Unix, cmd.exe on Windows
shell = "bun"
[test]
# Per-test timeout in milliseconds
timeout = 5000
# Collect coverage by default
coverage = false
# Coverage reporters: "text", "lcov"
coverageReporter = ["text"]
# Directory for coverage output
# coverageDir = "coverage"
Bun also reads tsconfig.json for:
- Path aliases (
compilerOptions.paths) — resolved automatically at runtime - JSX settings (
compilerOptions.jsx,jsxImportSource) — no build step needed
Environment Variables
Bun automatically loads .env files at startup in this priority order:
.env.local.env.development(or.env.productionwhenNODE_ENV=production).env
Access them via process.env or Bun.env:
// index.ts
console.log(process.env.DATABASE_URL);
console.log(Bun.env.DATABASE_URL); // same thing
Custom env file:
bun run --env-file .env.staging server.ts
Disable env file loading entirely:
bun run --no-env-file server.ts
Tip
Bun supports multiple
--env-fileflags in a single command, loaded in order. Later files take precedence for duplicate keys.
Per-Registry Authentication (bunfig.toml)
[install.scopes]
"@myorg" = { token = "$NPM_TOKEN", registry = "https://npm.pkg.github.com/" }
For private registries requiring auth, set credentials per scope rather than globally to avoid leaking tokens.
4. Core Usage
Running Scripts
Run a file directly
bun run index.ts
bun index.ts # shorthand — same result
TypeScript, JSX, and TSX work without any configuration:
bun run app.tsx
bun run utils.js
bun run server.mjs
Run package.json scripts
bun run dev
bun run build
bun run test
Note
bun runis significantly faster at script lookup and startup thannpm run. For a project with dozens of scripts, the difference is immediately noticeable.
Watch mode (restart on change)
bun run --watch index.ts
Bun watches the entry file and all imported modules. The process restarts cleanly on any change.
Hot reload (preserve state)
bun run --hot server.ts
Hot reload re-evaluates changed modules without restarting the process. HTTP servers stay up; in-memory state is preserved. Ideal for server development where you don't want to drop connections.
Difference between --watch and --hot:
| Mode | Process Restart | State Preserved | Best For |
|---|---|---|---|
--watch |
Yes | No | Scripts, CLIs, workers |
--hot |
No | Yes | HTTP servers, long-running services |
Evaluate inline code
bun --eval 'console.log("hello")'
bun -e 'console.log(process.version)'
# Print the result of an expression
bun --print 'Math.PI * 2'
bun -p '["a","b","c"].join(",")'
Run scripts in workspaces
# Run "build" in all workspaces matching the pattern
bun run --filter "packages/*" build
# Run in parallel (default behavior with --filter)
bun run --filter "*" test --parallel
# Run sequentially
bun run --filter "*" build --sequential
Package Management
Install all dependencies
bun install
bun i # alias
Key flags:
| Flag | Purpose |
|---|---|
--production / -p |
Skip devDependencies |
--frozen-lockfile |
Fail if lockfile would change (CI recommended) |
--dry-run |
Show what would be installed without installing |
--force / -f |
Re-download packages even if cached |
--no-cache |
Ignore cache entirely |
--global / -g |
Install to global package store |
--save-text-lockfile |
Save bun.lock (text) instead of bun.lockb (binary) |
--backend <mode> |
clonefile (default macOS), hardlink, symlink, copyfile |
--linker <mode> |
isolated (default) or hoisted (node_modules flat layout) |
--omit <type> |
Omit dev, optional, or peer deps |
--lockfile-only |
Only update lockfile, do not install to node_modules |
--trust |
Mark installed packages as trusted (for lifecycle scripts) |
--analyze / -a |
Show bundle size analysis after install |
Add a dependency
bun add react react-dom
bun add --dev typescript @types/node # devDependency
bun add --optional sharp # optionalDependency
bun add --peer react # peerDependency
bun add --exact react # pin to exact version (no ^)
bun add --only-missing react # add only if not already present
bun a zod # alias for add
Remove a dependency
bun remove react
bun rm react # alias
Update dependencies
bun update # update within semver ranges
bun update --latest # update to latest, ignoring semver ranges
bun update --interactive # choose per package interactively
bun update --recursive # update across all workspaces
Check for outdated packages
bun outdated
bun outdated --filter "packages/ui" # limit to a workspace
bun outdated --recursive # check all workspaces
Example output:
Package Current Update Latest
react 18.2.0 18.3.1 19.0.0
Security audit
bun audit # human-readable output
bun audit --json # machine-readable JSON
Run packages without installing
bunx cowsay "hello"
bun x cowsay "hello" # alias
# Force running with Bun (instead of Node.js shebang)
bunx --bun tsx script.ts
bunx downloads and caches the package, then runs it. The cache persists across invocations so subsequent runs are instant.
Link local packages
# In the package you want to link
bun link
# In the project that should use it
bun link my-local-package
Bundling
Basic bundle
bun build src/index.ts --outdir dist
Target environments
bun build src/index.ts --outdir dist --target browser # browser bundle
bun build src/index.ts --outdir dist --target bun # Bun runtime
bun build src/index.ts --outdir dist --target node # Node.js runtime
Output formats
bun build src/index.ts --outfile bundle.js --format esm # ES modules (default)
bun build src/index.ts --outfile bundle.js --format cjs # CommonJS
bun build src/index.ts --outfile bundle.js --format iife # IIFE for <script> tags
Code splitting
bun build src/index.ts --outdir dist --splitting
Produces multiple chunks that share code; useful for browser apps with dynamic imports.
Source maps
bun build src/index.ts --outdir dist --sourcemap linked # separate .map file, linked
bun build src/index.ts --outdir dist --sourcemap inline # embedded in output
bun build src/index.ts --outdir dist --sourcemap external # separate .map, not linked
bun build src/index.ts --outdir dist --sourcemap none # disabled
Minification
bun build src/index.ts --outdir dist --minify # all minification
bun build src/index.ts --outdir dist --minify-syntax # fold constants, simplify
bun build src/index.ts --outdir dist --minify-whitespace # remove whitespace
bun build src/index.ts --outdir dist --minify-identifiers # shorten variable names
Compile a standalone executable
bun build src/cli.ts --compile --outfile my-tool
./my-tool # runs without Bun installed
The resulting binary bundles the Bun runtime and your code. No external dependencies required on the target machine.
Windows executable options:
bun build src/app.ts --compile --outfile app.exe \
--windows-hide-console \
--windows-icon assets/icon.ico \
--windows-title "My App" \
--windows-publisher "Acme Corp" \
--windows-version "1.0.0" \
--windows-description "Does something useful" \
--windows-copyright "Copyright 2026"
Compile-time autoload control:
# Disable .env autoloading in the compiled binary
bun build src/app.ts --compile --no-compile-autoload-dotenv
# Control other autoloaded files
bun build src/app.ts --compile \
--no-compile-autoload-bunfig \
--no-compile-autoload-tsconfig \
--no-compile-autoload-package-json
Other build flags
| Flag | Purpose |
|---|---|
--external <pkg> |
Exclude package from bundle (treated as external) |
--watch |
Rebuild on file changes |
--metafile |
Emit metafile.json with bundle analysis |
--metafile-md |
Emit metafile.md (human-readable analysis) |
--banner <text> |
Prepend text to output files |
--footer <text> |
Append text to output files |
--public-path <url> |
URL prefix for asset references |
--packages <mode> |
external (exclude all npm) or bundle (include all) |
--env <mode> |
inline (embed env), prefix* (embed matching), disable |
--production |
Set NODE_ENV=production and enable full minification |
--no-bundle |
Transpile only — no bundling, preserves imports |
--keep-names |
Preserve function/class names after minification |
--bytecode |
Emit bytecode alongside JS for faster cold starts |
--define / -d |
Replace identifiers at build time |
--loader / -l |
Override file loader (e.g., .png:dataurl) |
Experimental:
# Bun Bake — full-stack web app bundler (experimental)
bun build --app
# Server Components (experimental)
bun build --app --server-components
Testing
Run all tests
bun test
Bun discovers test files matching **/*.test.{ts,tsx,js,jsx}, **/*.spec.{ts,tsx,js,jsx}, and **/test.{ts,js}.
Filter tests
bun test --test-name-pattern "auth" # run tests matching regex
bun test -t "should handle errors" # same, short flag
bun test src/auth.test.ts # run specific file
Test options
| Flag | Purpose |
|---|---|
--timeout <ms> |
Per-test timeout (default 5000) |
--update-snapshots / -u |
Overwrite snapshot files |
--bail <n> |
Stop after n failures |
--only |
Run only tests marked test.only() |
--todo |
Run tests marked test.todo() |
--rerun-each <n> |
Run each test n times (for flakiness detection) |
--retry <n> |
Retry failing tests up to n times |
--randomize |
Randomize test order |
--seed <int> |
Set random seed for reproducible randomization |
--pass-with-no-tests |
Exit 0 when no test files found |
--concurrent |
Run test files concurrently |
--max-concurrency <n> |
Limit concurrent test file execution |
--dots |
Minimal dot-style reporter |
--reporter <mode> |
junit or dots |
--reporter-outfile <path> |
Write reporter output to file |
--only-failures |
Re-run only previously failing tests |
--changed <ref> |
Run tests for files changed since git ref |
--path-ignore-patterns <glob> |
Exclude matching paths |
Coverage
bun test --coverage
bun test --coverage --coverage-reporter lcov
bun test --coverage --coverage-dir coverage/
Example output with coverage:
✓ src/math.test.ts > add > handles integers [0.42ms]
✓ src/math.test.ts > add > handles floats [0.12ms]
✓ src/math.test.ts > multiply > basic multiplication [0.08ms]
3 pass, 0 fail
Coverage report:
---------------------|---------|----------|---------|---------|
File | % Stmts | % Branch | % Funcs | % Lines |
---------------------|---------|----------|---------|---------|
src/math.ts | 100 | 100 | 100 | 100 |
---------------------|---------|----------|---------|---------|
Writing tests (Jest-compatible API)
import { describe, it, expect, beforeEach, afterAll, mock, spyOn } from "bun:test";
describe("UserService", () => {
let service: UserService;
beforeEach(() => {
service = new UserService();
});
it("creates a user", async () => {
const user = await service.create({ name: "Alice" });
expect(user.id).toBeDefined();
expect(user.name).toBe("Alice");
});
it("throws on duplicate email", () => {
expect(() => service.create({ email: "[email removed]" })).toThrow();
});
});
Bun's test runner supports: describe, it, test, expect, beforeAll, afterAll, beforeEach, afterEach, mock, spyOn, jest.fn(), jest.mock(), snapshot testing, and test.skip() / test.only() / test.todo().
Other Commands
| Command | Purpose |
|---|---|
bun init |
Scaffold a new project |
bun repl |
Start an interactive JavaScript REPL |
bun exec <script> |
Execute a shell script file |
bun upgrade |
Upgrade Bun to the latest version |
bun pm |
Package manager utilities (cache, bin paths, etc.) |
bun pm cache |
Manage the package cache |
bun pm bin |
Show path to local bin directory |
bun pm ls |
List installed packages |
bun pm trust |
Manage trusted packages for lifecycle scripts |
Global Runtime Flags
These flags work with bun run, bun test, bun build, and direct file execution:
| Flag | Purpose |
|---|---|
--env-file <path> |
Load environment variables from a specific file |
--no-env-file |
Disable automatic .env loading |
--cwd <path> |
Set working directory |
--config / -c <path> |
Use a specific bunfig.toml |
--watch |
Restart on file changes |
--hot |
Hot reload (preserve state) |
--smol |
Reduce memory usage (slower GC tradeoff) |
--preload <file> / -r |
Run a file before the entry point |
--inspect |
Enable debugger (default: ws://localhost:6499) |
--inspect-wait |
Enable debugger and wait for connection before starting |
--inspect-brk |
Enable debugger and pause on first line |
--cpu-prof* |
CPU profiling options |
--heap-prof* |
Heap profiling options |
--if-present |
Exit 0 if script not found in package.json |
--no-install |
Never auto-install missing modules |
--install <mode> |
auto, fallback, or force module resolution |
--port <n> |
Default HTTP port |
--conditions <list> |
Export condition overrides |
--no-macros |
Disable Bun macros |
--prefer-offline |
Use cache over network |
--prefer-latest |
Prefer latest version when resolving |
--max-http-header-size <n> |
Max HTTP header size in bytes |
--dns-result-order <mode> |
DNS result ordering (ipv4first, verbatim) |
--expose-gc |
Expose global.gc() |
--define / -d <k=v> |
Replace global identifiers |
--drop <identifier> |
Remove identifier references from code |
--loader / -l <ext:type> |
Override file loader |
--jsx-* |
JSX transform options |
--no-clear-screen |
Prevent clearing terminal in watch mode |
--unhandled-rejections <mode> |
throw, warn, ignore |
--console-depth <n> |
Max depth for console.log object inspection |
--title <name> |
Set process title |
--zero-fill-buffers |
Zero-fill Buffer allocations |
--use-system-ca |
Use system certificate store for TLS |
--use-openssl-ca |
Use OpenSSL certificate store |
--use-bundled-ca |
Use Bun's bundled CA certificates |
--redis-preconnect |
Open Redis connection at startup |
--sql-preconnect |
Open SQL connection at startup |
Key Runtime APIs
HTTP Server
import { serve } from "bun";
const server = Bun.serve({
port: 3000,
fetch(request) {
const url = new URL(request.url);
if (url.pathname === "/") {
return new Response("Hello, Bun!", {
headers: { "Content-Type": "text/plain" },
});
}
return new Response("Not Found", { status: 404 });
},
});
console.log(`Listening on http://localhost:${server.port}`);
File I/O
// Read a file
const file = Bun.file("./data.json");
const data = await file.json();
const text = await Bun.file("./readme.txt").text();
const buffer = await Bun.file("./image.png").arrayBuffer();
// Check existence
const exists = await Bun.file("./config.yaml").exists();
// File stats
const stat = await Bun.file("./log.txt").stat();
console.log(stat.size, stat.mtime);
// Write a file
await Bun.write("./output.txt", "Hello, World!");
await Bun.write("./data.json", JSON.stringify({ key: "value" }, null, 2));
// Streaming write
const writer = Bun.file("./large.txt").writer();
writer.write("chunk 1\n");
writer.write("chunk 2\n");
await writer.end();
// Delete a file
await Bun.file("./temp.txt").delete();
Shell Scripting
import { $ } from "bun";
// Run a command, get output
const result = await $`ls -la`.text();
console.log(result);
// Pipe between commands
const count = await $`cat package.json | wc -l`.text();
// Capture stderr separately
const { stdout, stderr } = await $`git status`.quiet();
// Use template literals with variables (safe — no injection)
const filename = "my file.txt";
await $`rm ${filename}`;
// Run in a specific directory
await $`npm install`.cwd("/tmp/my-project");
Database — PostgreSQL/MySQL
import { sql } from "bun";
// Bun reads DATABASE_URL from environment
const users = await sql`SELECT * FROM users WHERE active = ${true}`;
// Transactions
await sql.transaction(async (tx) => {
await tx`INSERT INTO orders (user_id, total) VALUES (${userId}, ${total})`;
await tx`UPDATE users SET order_count = order_count + 1 WHERE id = ${userId}`;
});
Format Parsers
// TOML
const config = Bun.TOML.parse(`[server]\nport = 3000`);
// YAML
const yaml = Bun.YAML.parse("name: bun\nversion: 1.3.13");
const yamlStr = Bun.YAML.stringify({ name: "bun" });
// JSONC (JSON with Comments)
const jsoncData = Bun.JSONC.parse(`{ // comment\n "key": "value" }`);
// JSON5
const json5 = Bun.JSON5.parse(`{ key: 'value', /* comment */ }`);
const json5Str = Bun.JSON5.stringify({ key: "value" });
// JSONL (JSON Lines)
const lines = Bun.JSONL.parse(`{"a":1}\n{"b":2}\n`);
// lines = [{ a: 1 }, { b: 2 }]
Markdown Rendering
// Render to HTML
const html = Bun.markdown.html("# Hello\n\nThis is **bold**.");
// Render to ANSI terminal output
const terminal = Bun.markdown.ansi("# Hello\n\nThis is **bold**.");
// Render with options
const rendered = Bun.markdown.render("## Section\n\nContent.");
// Render to React elements
const reactEl = Bun.markdown.react("## Section");
Password Hashing
// Hash a password (uses bcrypt by default)
const hash = await Bun.password.hash("my-secret-password");
// Verify
const isValid = await Bun.password.verify("my-secret-password", hash);
// true
// Explicit algorithm
const bcryptHash = await Bun.password.hash("password", { algorithm: "bcrypt", cost: 10 });
const argon2Hash = await Bun.password.hash("password", { algorithm: "argon2id" });
Non-Crypto Hashing
// Fast hashing for hash tables, checksums
const hash1 = Bun.hash.wyhash("hello world");
const hash2 = Bun.hash.crc32("data");
const hash3 = Bun.hash.xxHash64("large data");
const hash4 = Bun.hash.murmur32("input");
const hash5 = Bun.hash.cityHash32("input");
Terminal String Utilities
Bun.stringWidth("hello"); // 5 (handles unicode/emoji width)
Bun.stripANSI("\x1b[31mred\x1b[0m"); // "red"
Bun.sliceAnsi("\x1b[31mhello\x1b[0m", 0, 3); // "\x1b[31mhel\x1b[0m"
Bun.wrapAnsi("long text...", 40); // word-wrap preserving ANSI codes
CSRF Protection
const token = Bun.CSRF.generate();
const isValid = Bun.CSRF.verify(token, storedToken);
Web Workers
const worker = new Worker("./worker.ts", {
smol: true, // reduced memory mode
preload: "./setup.ts", // run before worker code
env: { API_URL: "https://api.example.com" }, // isolated env
});
worker.postMessage({ task: "compute", data: [1, 2, 3] });
worker.onmessage = (e) => console.log(e.data);
5. Common Tasks
Create and Run a TypeScript Project
mkdir my-service && cd my-service
bun init -y
Edit index.ts:
const greeting = (name: string): string => `Hello, ${name}!`;
console.log(greeting("Bun"));
bun run index.ts
# Hello, Bun!
No tsc, no ts-node, no build step.
Set Up an HTTP Server
// server.ts
Bun.serve({
port: 3000,
async fetch(req) {
const url = new URL(req.url);
if (req.method === "GET" && url.pathname === "/health") {
return Response.json({ status: "ok", uptime: process.uptime() });
}
if (req.method === "POST" && url.pathname === "/echo") {
const body = await req.json();
return Response.json(body);
}
return new Response("Not Found", { status: 404 });
},
});
bun run --hot server.ts
# Listening on http://localhost:3000
Modify server.ts and save — the server reloads without dropping connections.
Install Packages and Manage Dependencies
# Add production dependencies
bun add express zod
# Add development dependencies
bun add --dev @types/express vitest
# Install all deps (e.g., after cloning a repo)
bun install
# Install for CI (fail if lockfile would change)
bun install --frozen-lockfile
# Install production only (skip devDeps)
bun install --production
Bun creates bun.lockb (binary lockfile) or bun.lock (text, with --save-text-lockfile). Commit either to version control for reproducible installs.
Bundle for Production
# Browser bundle with minification
bun build src/index.ts \
--outdir dist \
--target browser \
--minify \
--sourcemap linked \
--production
# Check what was emitted
ls dist/
# index.js index.js.map metafile.json
With code splitting for a multi-page app:
bun build src/pages/*.ts \
--outdir dist \
--target browser \
--splitting \
--format esm
Create a Standalone Executable
// cli.ts
const args = process.argv.slice(2);
if (args[0] === "--help") {
console.log("Usage: my-tool [options]");
process.exit(0);
}
console.log("Running with args:", args);
bun build cli.ts --compile --outfile my-tool
# Test it — no Bun needed on target machine
./my-tool --help
# Usage: my-tool [options]
# Check the binary
file my-tool
# my-tool: ELF 64-bit LSB executable, x86-64, ...
Run Tests with Coverage
# Run all tests with coverage
bun test --coverage
# CI-friendly: fail if coverage drops below threshold, use lcov for reporting
bun test \
--coverage \
--coverage-reporter lcov \
--coverage-dir coverage \
--bail 1
Configure default coverage behavior in bunfig.toml:
[test]
coverage = true
coverageReporter = ["text", "lcov"]
coverageDir = "coverage"
Use Hot Reloading During Development
# Start server with hot reload
bun run --hot src/server.ts
# In another terminal, watch your changes take effect live
curl http://localhost:3000/health
For scripts and CLIs where you want a clean restart:
bun run --watch src/worker.ts
Parse Environment Variables from .env Files
# .env
DATABASE_URL=postgresql://localhost/mydb
PORT=3000
NODE_ENV=development
// These are automatically available — no dotenv package needed
const db = Bun.env.DATABASE_URL;
const port = parseInt(Bun.env.PORT ?? "3000");
# Use a different env file
bun run --env-file .env.staging src/server.ts
# Disable env file loading
bun run --no-env-file src/server.ts
Use Bun as a Drop-In Node.js Replacement
For most Node.js projects, Bun works without any changes:
# Replace node with bun
bun src/app.js
# Replace npm scripts
bun run start
bun run dev
# Replace npx
bunx create-react-app my-app
Bun implements the Node.js API (fs, path, http, crypto, stream, etc.) and resolves node:* imports. CommonJS (require()) and ESM (import) both work, and can be mixed in the same project.
Important
Some native Node.js addons (
.nodefiles) may not be compatible with Bun. Check the Bun compatibility tracker atbun.sh/nodejsfor the current status of any modules you depend on.
Execute Shell Commands with Bun.$
import { $ } from "bun";
// Build pipeline
await $`bun install`;
await $`bun build src/index.ts --outdir dist --minify`;
await $`tar -czf dist.tar.gz dist/`;
// Read output
const branch = await $`git rev-parse --abbrev-ref HEAD`.text();
console.log(`Current branch: ${branch.trim()}`);
// Check exit code without throwing
const result = await $`test -f .env`.quiet().nothrow();
if (result.exitCode !== 0) {
console.warn("No .env file found, using defaults");
}
// Pipe data in
const json = JSON.stringify({ key: "value" });
await $`cat`.stdin(json).text();
Use Workspaces in a Monorepo
// package.json (root)
{
"name": "my-monorepo",
"workspaces": ["packages/*", "apps/*"]
}
# Install all workspace deps at once
bun install
# Run a script in all workspaces
bun run --filter "*" build
# Run in specific workspaces
bun run --filter "packages/ui" test
bun run --filter "apps/*" dev --parallel
Each workspace gets its own node_modules/.bin entries, but Bun deduplicates packages at the top level.
Connect to PostgreSQL with the Built-In SQL Client
import { sql } from "bun";
// Set DATABASE_URL in your .env
// DATABASE_URL=postgresql://user:pass@localhost:5432/mydb
// Query
const users = await sql`
SELECT id, name, email
FROM users
WHERE created_at > ${new Date(Date.now() - 86400_000)}
ORDER BY created_at DESC
LIMIT 10
`;
// Parameterized inserts
const [newUser] = await sql`
INSERT INTO users (name, email)
VALUES (${name}, ${email})
RETURNING *
`;
console.log(newUser.id);
For MySQL:
const db = new Bun.sql("mysql://user:pass@localhost:3306/mydb");
const rows = await db`SELECT * FROM products LIMIT 5`;
Render Markdown to HTML
const markdown = `
# Getting Started
Install Bun with:
\`\`\`bash
curl -fsSL https://bun.sh/install | bash
\`\`\`
Then run any TypeScript file **directly**.
`;
const html = Bun.markdown.html(markdown);
// Returns fully-rendered HTML string
// In a Bun.serve() handler:
Bun.serve({
port: 3000,
fetch() {
return new Response(html, {
headers: { "Content-Type": "text/html; charset=utf-8" },
});
},
});
6. Troubleshooting
Error: Cannot find module './utils'
Cause: Bun resolves modules according to Node.js resolution rules. Missing file extension or incorrect path casing on case-sensitive filesystems.
Solution:
- Ensure the import path matches the filename exactly (case-sensitive on Linux).
- Add the file extension if using
.tsfiles that import other.tsfiles:import { foo } from './utils.ts'— Bun supports explicit.tsextensions in imports. - Check that
tsconfig.jsonpathsaliases are defined correctly if using aliased imports.
Prevention: Use an editor with TypeScript support (VS Code + the Bun extension) to catch missing module errors at edit time.
Error: EPERM: operation not permitted during bun install
Cause: Tracked in issue #29398. Occurs when patching packages on Windows or when the --backend uses filesystem operations that require elevated permissions.
Solution:
- On Windows, try switching the install backend:
bun install --backend copyfile - Ensure no antivirus or security software is holding file locks on
node_modules. - Delete
node_modulesandbun.lockb, then retry:rm -rf node_modules bun.lockb && bun install
Error: EADDRINUSE: Address already in use in VS Code
Cause: Tracked in issue #16242. The integrated VS Code terminal can hold open a previous Bun process's port, especially when stopping a dev server with Ctrl+C.
Solution:
- Kill the process holding the port:
lsof -ti:3000 | xargs kill -9(macOS/Linux) or use Task Manager on Windows. - Use a different port:
bun run --port 3001 server.ts - Switch to an external terminal for running Bun dev servers.
Error: Segmentation fault when using FFI on Linux x64
Cause: Tracked in issue #29346. Certain FFI (Foreign Function Interface) call patterns cause a segfault on Linux x64 in Bun 1.x.
Solution:
- Check if your FFI signature matches the native library's actual ABI exactly (including calling convention).
- Ensure all pointer types are declared correctly in
FFIType. - If the segfault is reproducible and your FFI code looks correct, file a bug on the Bun GitHub tracker referencing issue #29346.
Error: Segmentation fault in HTTP server
Cause: Tracked in issue #15281. Certain request patterns or server configurations can cause a server crash in specific Bun versions.
Solution:
- Upgrade Bun:
bun upgrade - Wrap your fetch handler in a try/catch to prevent unhandled errors from crashing the process.
- Enable the
--watchflag during development so the process auto-restarts.
Error: Codesign failure with bun build --compile on macOS
Cause: Tracked in issue #29361. A regression introduced codesigning issues for compiled binaries on macOS.
Solution:
- Upgrade Bun:
bun upgrade - Manually re-sign the binary:
codesign --force --deep --sign - ./my-binary - If distributing, use a proper Apple Developer ID signature.
Error: bun install not linking bin from a package (e.g., @anthropic-ai/claude-code)
Cause: Tracked in issue #29399. Certain packages with bin fields in their package.json are not correctly linked into .bin/ after install.
Solution:
- Run
bun pm binto confirm the bin directory location. - Delete
node_modulesand reinstall:rm -rf node_modules && bun install - Add
--trustflag for packages with lifecycle scripts:bun add --trust @anthropic-ai/claude-code - As a workaround, run the package directly:
bunx @anthropic-ai/claude-code
Error: Drive root path failure on Windows
Cause: Tracked in issue #29273. Paths beginning at a Windows drive root (e.g., C:\) may not resolve correctly in certain Bun APIs.
Solution:
- Use forward slashes:
C:/Users/...instead ofC:\Users\... - Use relative paths where possible.
- Upgrade Bun:
bun upgrade
Error: File.type returns wrong charset
Cause: Tracked in issue #29257. BunFile.type (MIME type) may include an incorrect or missing charset component for certain file types.
Solution:
- Explicitly set the
Content-Typeheader in HTTP responses rather than relying onBun.file().type. - Use
new Response(body, { headers: { "Content-Type": "text/html; charset=utf-8" } }).
Memory Leak with MongoDB
Cause: Tracked in issue #12117. A memory leak occurs when using the mongodb npm package with Bun under sustained connection load.
Solution:
- Implement connection pooling and explicitly close connections when done.
- Monitor memory with
process.memoryUsage()to confirm the leak. - Consider using a managed connection pool library (e.g.,
mongoosewithautoIndex: falseto reduce connection churn). - Check for a newer version of
mongodbthat may have patched the underlying issue. - File a reproduction on the Bun GitHub tracker referencing issue #12117 if the leak persists on current versions.
100% CPU Usage When Debugging
Cause: Tracked in issue #21654. The Bun debugger (--inspect) can spin at 100% CPU in certain debug session configurations.
Solution:
- Use
--inspect-waitinstead of--inspectto ensure the debugger attaches before execution begins. - Set breakpoints before resuming execution.
- Disconnect and reconnect the debugger if CPU usage spikes.
- Upgrade Bun:
bun upgrade
Lockfile Conflicts (bun.lockb vs bun.lock)
Cause: Projects switching between binary (bun.lockb) and text (bun.lock) lockfile formats, or teams using different Bun versions, can encounter conflicts.
Solution:
- Agree on one format. Add
save-text-lockfile = truetobunfig.tomlto use the text format for easier code review. - Delete the old lockfile and regenerate:
rm bun.lockb && bun install --save-text-lockfile - In CI, always use
--frozen-lockfileto fail fast on drift.
bunfig.toml Parse Errors
Cause: TOML syntax errors or incorrect types in bunfig.toml.
Solution:
- Validate your TOML at
tomllib.devor withpython3 -c "import tomllib; tomllib.loads(open('bunfig.toml').read())". - Common mistake: using
"true"(string) instead oftrue(boolean). - Check for stray commas — TOML (unlike JSON) does not allow trailing commas.
Example of a correct section:
[install]
production = false # boolean, not "false"
frozen-lockfile = false # boolean
Error: TypeScript path aliases not resolving at runtime
Cause: tsconfig.json path aliases are not configured in bunfig.toml or in a way Bun recognizes.
Solution: Bun reads compilerOptions.paths from tsconfig.json automatically. Ensure your tsconfig.json is at the project root and the baseUrl is set:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@utils/*": ["src/utils/*"],
"@types/*": ["src/types/*"]
}
}
}
No additional Bun configuration is needed.
Error: bun: command not found after install
Cause: The Bun binary directory (~/.bun/bin) is not in your PATH.
Solution:
# Add to ~/.bashrc or ~/.zshrc:
export BUN_INSTALL="$HOME/.bun"
export PATH="$BUN_INSTALL/bin:$PATH"
# Then reload:
source ~/.bashrc
7. FAQ
How do I switch from npm to Bun?
Drop-in replacement: run bun install instead of npm install, bun run <script> instead of npm run <script>, and bun add <pkg> instead of npm install <pkg>. Your package.json and existing node_modules structure remain unchanged. Bun reads package.json the same way npm does.
How do I switch from yarn or pnpm to Bun?
The same principle applies. Bun understands yarn.lock and pnpm-lock.yaml and can migrate from them: running bun install will replace either lockfile with bun.lockb. Delete the old lockfile after migrating to avoid confusion.
Is Bun compatible with Node.js?
Bun implements the Node.js API surface including fs, path, http, https, crypto, stream, buffer, child_process, worker_threads, os, net, and more. Most Node.js code runs without modification. Gaps and known incompatibilities are tracked at bun.sh/nodejs. Native addons (.node files compiled for Node.js) may not work.
Does Bun work on Windows?
Yes. Bun has full Windows support for x64 and arm64. The built-in shell (Bun.$) handles cross-platform scripting so you don't need WSL for most tasks. Install via scoop install bun or the npm package (npm install -g bun).
How do I use Bun with Docker?
Use the official oven/bun images. A minimal production Dockerfile:
FROM oven/bun:1.3.13-alpine AS builder
WORKDIR /app
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile --production
COPY src/ ./src/
FROM oven/bun:1.3.13-distroless
WORKDIR /app
COPY --from=builder /app .
CMD ["bun", "run", "src/server.ts"]
What file types does Bun support natively?
.js, .jsx, .ts, .tsx, .mjs, .cjs, .mts, .cts — all executed natively without plugins. Bun also handles .json (imported as objects), .toml (imported as objects), .txt (imported as strings), and .html (in the experimental Bun Bake bundler).
How do I debug with VS Code? Start Bun with the inspector enabled:
bun run --inspect-wait src/server.ts
In VS Code, open the Run and Debug panel, create a launch.json with type "bun", and attach to ws://localhost:6499. The Bun VS Code extension (available in the marketplace) automates this configuration.
Warning
Running Bun with
--inspectmay cause 100% CPU usage in some versions (issue #21654). Use--inspect-waitand set breakpoints before resuming to mitigate this.
How do I generate a standalone executable?
Use bun build --compile:
bun build src/cli.ts --compile --outfile my-tool
The resulting binary includes the Bun runtime and your code. It runs on any machine with the same OS and architecture — no Bun, Node.js, or npm required.
What's the difference between --watch and --hot?
--watch restarts the process cleanly on file changes (new process, fresh state). --hot hot-patches changed modules into the running process (same process, preserved state). Use --watch for CLIs and scripts; use --hot for HTTP servers where you don't want to drop connections.
How do I use workspaces in a monorepo?
Add a workspaces field to your root package.json:
{ "workspaces": ["packages/*", "apps/*"] }
Then bun install at the root installs all workspace dependencies. Use bun run --filter "packages/*" build to run scripts across workspaces.
Can Bun replace webpack or esbuild?
For most use cases, yes. bun build handles tree-shaking, code splitting, source maps, minification, and targets (browser, Node.js, Bun). It reads tsconfig.json path aliases. For highly customized webpack configs with obscure loaders, you may need to migrate plugin logic, but Bun's build API supports custom plugins written in TypeScript.
Does Bun support ESM and CommonJS?
Both. You can import and require() in the same file. Bun resolves .mjs as ESM, .cjs as CommonJS, and .js based on the "type" field in package.json. ESM packages that conditionally export based on "exports" are fully supported.
How do I use Bun with an existing Node.js project?
- Install Bun:
curl -fsSL https://bun.sh/install | bash - In your project directory:
bun install(replacesnpm install) - Replace
node src/server.jswithbun src/server.jsin your scripts - Test thoroughly — most projects work without changes
How do I configure test coverage thresholds?
Bun does not yet enforce minimum coverage thresholds via CLI flags (use a separate tool like nyc for enforcement). However, you can fail the CI pipeline by checking the text output:
bun test --coverage 2>&1 | tee coverage.txt
# Parse coverage.txt for your threshold check
Configure reporters and output directory in bunfig.toml:
[test]
coverage = true
coverageReporter = ["text", "lcov"]
coverageDir = "coverage"
What is bunfig.toml?
bunfig.toml is Bun's configuration file, analogous to .npmrc or jest.config.ts. It configures the package manager ([install]), script runner ([run]), test runner ([test]), and more. Place it at the project root or at ~/.bunfig.toml for global defaults. Bun merges both files, with the project-level file taking precedence.
What's the difference between bun run and bun exec?
bun run <file> runs a JavaScript/TypeScript file using Bun's runtime, or a script name from package.json. bun exec <script.sh> executes a shell script file using Bun's built-in cross-platform shell — equivalent to running bash script.sh but without requiring bash on the system, making it useful for cross-platform scripts on Windows.
How do I use the built-in Redis client?
Set REDIS_URL in your environment and use Bun.redis:
const client = new Bun.RedisClient("redis://localhost:6379");
await client.set("key", "value");
const val = await client.get("key");
// "value"
await client.disconnect();
// Or use the default client (reads REDIS_URL)
await Bun.redis.set("count", 1);
How do I use the built-in S3 client?
const s3 = new Bun.S3Client({
region: "us-east-1",
accessKeyId: Bun.env.AWS_ACCESS_KEY_ID,
secretAccessKey: Bun.env.AWS_SECRET_ACCESS_KEY,
bucket: "my-bucket",
});
// Upload
await s3.write("uploads/image.png", imageBuffer);
// Download
const file = s3.file("uploads/image.png");
const data = await file.arrayBuffer();
// Or use the default client (reads AWS_* env vars)
const file = Bun.s3("s3://my-bucket/key.txt");
How do I transpile code programmatically?
const transpiler = new Bun.Transpiler({
loader: "ts",
target: "browser",
});
const result = await transpiler.transform(`
const greet = (name: string) => \`Hello, \${name}!\`;
export { greet };
`);
console.log(result);
// const greet = (name) => `Hello, ${name}!`;
// export { greet };