Getting started
Installation
# macOS/Linux (curl)
curl -fsSL https://bun.sh/install | bash
# macOS (Homebrew)
brew install oven-sh/bun/bun
# Windows (PowerShell)
powershell -c "irm bun.sh/install.ps1 | iex"
# Docker
docker pull oven/bun
docker run --rm oven/bun --version
# Upgrade to latest
bun upgrade
Quick Start
# Initialize new project
bun init
# Run TypeScript directly
bun index.ts
# Run with watch mode
bun --watch index.ts
# Hot reload mode
bun --hot index.ts
First Script
// server.ts
const server = Bun.serve({
port: 3000,
fetch(req) {
return new Response("Hello from Bun!");
},
});
console.log(`Listening on ${server.url}`);
bun server.ts
# ⚡ 3x faster startup than Node.js
Running Code
Basic Execution
# Run JavaScript
bun run index.js
# Run TypeScript (no compilation needed)
bun run index.ts
# Run package.json script
bun run dev
bun run build
bun run test
# Short form
bun dev
bun build
bun test
Watch Mode
# Auto-restart on file changes
bun --watch server.ts
# Watch with specific files
bun --watch src/**/*.ts server.ts
# Hot reload (faster, no restart)
bun --hot server.ts
REPL
# Interactive shell
bun repl
# Execute and print
bun -e "console.log('Hello')"
# Pipe input
echo "2 + 2" | bun -
Package Management
Installing Packages
# Install from package.json
bun install
# Add package
bun add react
bun add -d typescript
bun add --dev vitest
# Add specific version
bun add react@18.2.0
# Add from GitHub
bun add github:user/repo
# Global install
bun add -g typescript
Removing Packages
# Remove package
bun remove react
# Remove dev dependency
bun remove -d typescript
Package Info
# List installed packages
bun pm ls
# Check outdated
bun outdated
# Clean cache
bun pm cache rm
bunx (npx alternative)
# Run package without installing
bunx create-react-app my-app
bunx tsc --init
bunx cowsay "Hello Bun!"
# Faster than npx - no install delay
Bundler
Basic Bundling
# Bundle single file
bun build ./index.ts --outdir ./dist
# Bundle with minification
bun build ./index.ts --minify --outfile ./dist/app.js
# Watch mode
bun build ./index.ts --watch --outdir ./dist
Advanced Options
# Target platforms
bun build --target browser ./index.ts
bun build --target node ./index.ts
bun build --target bun ./index.ts
# Splitting
bun build --splitting ./index.ts
# Source maps
bun build --sourcemap ./index.ts
# Multiple entry points
bun build ./src/*.ts --outdir ./dist
Build Configuration
// build.ts
await Bun.build({
entrypoints: ["./index.ts"],
outdir: "./dist",
target: "browser",
minify: true,
splitting: true,
sourcemap: "external",
external: ["react", "react-dom"],
});
Performance
| Tool | Build Time |
|---|---|
| Bun | 0.1s |
| esbuild | 0.2s |
| Webpack | 2.5s |
| Parcel | 3.1s |
Testing
Basic Testing
// math.test.ts
import { expect, test, describe } from "bun:test";
describe("math", () => {
test("addition", () => {
expect(2 + 2).toBe(4);
});
test("subtraction", () => {
expect(5 - 3).toBe(2);
});
});
Running Tests
# Run all tests
bun test
# Watch mode
bun test --watch
# Run specific file
bun test math.test.ts
# Run with coverage
bun test --coverage
# Bail on first failure
bun test --bail
Matchers
import { expect, test } from "bun:test";
test("matchers", () => {
// Equality
expect(value).toBe(4);
expect(obj).toEqual({ a: 1 });
// Truthiness
expect(value).toBeTruthy();
expect(value).toBeFalsy();
expect(value).toBeNull();
expect(value).toBeUndefined();
// Numbers
expect(value).toBeGreaterThan(3);
expect(value).toBeLessThanOrEqual(5);
expect(0.1 + 0.2).toBeCloseTo(0.3);
// Strings
expect(str).toMatch(/pattern/);
expect(str).toContain("substring");
// Arrays
expect(arr).toContain("item");
expect(arr).toHaveLength(3);
// Promises
await expect(promise).resolves.toBe(value);
await expect(promise).rejects.toThrow();
});
Lifecycle Hooks
import { beforeAll, beforeEach, afterEach, afterAll, test } from "bun:test";
beforeAll(() => {
// Run once before all tests
});
beforeEach(() => {
// Run before each test
});
afterEach(() => {
// Run after each test
});
afterAll(() => {
// Run once after all tests
});
test("with setup/teardown", () => {
// Test code
});
Mocking
import { mock, spyOn, test, expect } from "bun:test";
test("mock function", () => {
const fn = mock(() => "mocked");
expect(fn()).toBe("mocked");
expect(fn).toHaveBeenCalled();
expect(fn).toHaveBeenCalledTimes(1);
});
test("spy on method", () => {
const obj = { method: () => "original" };
const spy = spyOn(obj, "method");
obj.method();
expect(spy).toHaveBeenCalled();
spy.mockRestore();
});
Bun APIs
HTTP Server (Bun.serve)
// Fast HTTP server
const server = Bun.serve({
port: 3000,
fetch(req) {
const url = new URL(req.url);
if (url.pathname === "/") {
return new Response("Home");
}
if (url.pathname === "/api") {
return Response.json({ status: "ok" });
}
return new Response("Not Found", { status: 404 });
},
});
console.log(`Server: ${server.url}`);
File I/O (Bun.file)
// Read file
const file = Bun.file("package.json");
const text = await file.text();
const json = await file.json();
const buffer = await file.arrayBuffer();
// Check file exists
const exists = await file.exists();
// File metadata
const size = file.size;
const type = file.type;
// Stream file
const stream = file.stream();
Writing Files (Bun.write)
// Write string
await Bun.write("output.txt", "Hello Bun!");
// Write JSON
await Bun.write("data.json", JSON.stringify({ key: "value" }));
// Write Response
const response = await fetch("https://example.com");
await Bun.write("page.html", response);
// Write with options
await Bun.write("file.txt", "content", {
createPath: true,
});
Hashing (Bun.hash)
// Fast hashing
import { hash } from "bun";
// Wyhash (default, fastest)
hash("Hello Bun!"); // Returns number
hash.wyhash("Hello Bun!");
// CityHash
hash.cityHash64("Hello Bun!");
// Murmur
hash.murmur32v3("Hello Bun!");
hash.murmur64v2("Hello Bun!");
Password Hashing
// Bcrypt
const password = "secret123";
const hashed = await Bun.password.hash(password);
const isValid = await Bun.password.verify(password, hashed);
// Argon2
const hashed2 = await Bun.password.hash(password, {
algorithm: "argon2id",
memoryCost: 65536,
timeCost: 3,
});
Environment Variables
// Access env vars
const apiKey = Bun.env.API_KEY;
const port = Bun.env.PORT || 3000;
// Load .env file (automatic)
// .env file is loaded by default
console.log(Bun.env.DATABASE_URL);
// All env vars
console.log(Bun.env);
Process Info
// Bun version
console.log(Bun.version);
// Bun revision
console.log(Bun.revision);
// Main file path
console.log(Bun.main);
// Platform
console.log(process.platform);
console.log(process.arch);
TypeScript
Built-in Support
// No configuration needed
// Just run TypeScript files directly
// server.ts
interface User {
id: number;
name: string;
}
const users: User[] = [{ id: 1, name: "Alice" }];
Bun.serve({
fetch() {
return Response.json(users);
},
});
# No tsc, no compilation step
bun server.ts
TypeScript Config
// tsconfig.json (optional)
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"lib": ["ESNext"],
"types": ["bun-types"],
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"noEmit": true,
"strict": true
}
}
Type Definitions
# Install Bun types
bun add -d bun-types
# Add to tsconfig.json
# "types": ["bun-types"]
// Now get full IDE support
const server = Bun.serve({
// Full autocomplete for options
});
JSX Support
// Automatic JSX support
const element = <div>Hello</div>;
// Configure in tsconfig.json
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "react"
}
}
Web APIs
Fetch API
// Built-in fetch (fast)
const response = await fetch("https://api.example.com/data");
const data = await response.json();
// With options
const response = await fetch("https://api.example.com/users", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ name: "Alice" }),
});
// FormData
const formData = new FormData();
formData.append("file", file);
await fetch("/upload", {
method: "POST",
body: formData,
});
WebSocket
// Client
const ws = new WebSocket("ws://localhost:3000");
ws.addEventListener("open", () => {
ws.send("Hello Server!");
});
ws.addEventListener("message", (event) => {
console.log("Message:", event.data);
});
// Server
Bun.serve({
fetch(req, server) {
if (server.upgrade(req)) {
return; // Upgraded to WebSocket
}
return new Response("Upgrade failed", { status: 500 });
},
websocket: {
open(ws) {
console.log("Client connected");
},
message(ws, message) {
ws.send(`Echo: ${message}`);
},
close(ws) {
console.log("Client disconnected");
},
},
});
Streams
// ReadableStream
const stream = new ReadableStream({
start(controller) {
controller.enqueue("chunk 1");
controller.enqueue("chunk 2");
controller.close();
},
});
// Stream response
Bun.serve({
fetch() {
return new Response(stream);
},
});
// File streaming
const file = Bun.file("large.json");
return new Response(file.stream());
Web Crypto
// Generate random values
const array = new Uint8Array(16);
crypto.getRandomValues(array);
// UUID
const uuid = crypto.randomUUID();
// Hashing
const data = new TextEncoder().encode("Hello");
const hash = await crypto.subtle.digest("SHA-256", data);
// HMAC
const key = await crypto.subtle.generateKey(
{ name: "HMAC", hash: "SHA-256" },
false,
["sign", "verify"],
);
Node.js Compatibility
Module System
// ESM (preferred)
import fs from "fs";
import { readFile } from "fs/promises";
// CommonJS (supported)
const fs = require("fs");
// node: prefix (recommended)
import fs from "node:fs";
import path from "node:path";
Core Modules
// Most Node modules work
import fs from "node:fs/promises";
import path from "node:path";
import { Buffer } from "node:buffer";
import crypto from "node:crypto";
import http from "node:http";
// Use Bun APIs when available (faster)
// ✗ const data = await fs.readFile("file.txt");
// ✓ const data = await Bun.file("file.txt").text();
Key Differences
| Feature | Node.js | Bun |
|---|---|---|
| TypeScript | Needs compilation | Native support |
| Package manager | npm/yarn/pnpm | Built-in (faster) |
| Test runner | Jest/Vitest | Built-in (faster) |
| Bundler | Webpack/esbuild | Built-in (faster) |
| .env loading | Needs dotenv | Automatic |
| Watch mode | Needs nodemon | Built-in --watch |
npm Compatibility
# Most npm packages work
bun add express
bun add react react-dom
# Some packages need adjustments
# Check Bun compatibility: bun.sh/guides/ecosystem
Migration Tips
// Replace Node-specific code
// Node.js
import { readFile } from "fs/promises";
const data = await readFile("file.txt", "utf8");
// Bun (faster)
const data = await Bun.file("file.txt").text();
// Node.js
import { createServer } from "http";
const server = createServer((req, res) => {
res.end("Hello");
});
// Bun (faster)
Bun.serve({
fetch(req) {
return new Response("Hello");
},
});
Performance
Speed Comparisons
| Task | Node.js | Bun | Speedup |
|---|---|---|---|
| Cold start | 100ms | 30ms | 3.3x |
| bun install | 15s | 1s | 15x |
| HTTP req/sec | 50k | 130k | 2.6x |
| FFI calls | 1M/s | 10M/s | 10x |
| Bundling | 2.5s | 0.1s | 25x |
Optimization Tips
// 1. Use Bun APIs over Node.js APIs
// ✓ Fast
await Bun.file("data.json").json();
// ✗ Slower
await fs.readFile("data.json", "utf8").then(JSON.parse);
// 2. Use Bun.serve over http.createServer
// ✓ 2.6x faster
Bun.serve({
fetch(req) {
return new Response();
},
});
// ✗ Slower
http.createServer((req, res) => {
res.end();
});
// 3. Use Bun.write for file operations
// ✓ Optimized
await Bun.write("out.txt", "data");
// ✗ Slower
await fs.writeFile("out.txt", "data");
Memory Usage
// Efficient file streaming
const file = Bun.file("large.mp4");
// Stream to response (low memory)
return new Response(file.stream());
// Don't load entire file
// ✗ High memory
const buffer = await file.arrayBuffer();
return new Response(buffer);
Benchmarking
// Built-in performance API
const start = performance.now();
// Code to benchmark
await heavyOperation();
const end = performance.now();
console.log(`Took ${end - start}ms`);
// Console timing
console.time("operation");
await operation();
console.timeEnd("operation");
Package Configuration
package.json Scripts
{
"name": "my-app",
"version": "1.0.0",
"scripts": {
"dev": "bun --watch index.ts",
"build": "bun build index.ts --outdir dist --minify",
"start": "bun index.ts",
"test": "bun test",
"test:watch": "bun test --watch"
},
"dependencies": {
"express": "^4.18.2"
},
"devDependencies": {
"bun-types": "latest"
}
}
bunfig.toml
# Bun configuration file
[install]
# Registry configuration
registry = "https://registry.npmjs.org/"
# Scoped registries
# registry = { "https://npm.local.com": { token: "123" }}
# Cache directory
cache = "~/.bun/cache"
# Optional peer dependencies
optional = true
# Install exact versions
exact = true
[test]
# Test runner configuration
coverage = true
coverageThreshold = 80
[run]
# Shell to use for scripts
shell = "bash"
Workspaces
// package.json
{
"name": "monorepo",
"workspaces": ["packages/*"]
}
# Install all workspace dependencies
bun install
# Run script in workspace
bun run --filter @myorg/package-a build
# Add dependency to workspace
bun add react --filter @myorg/package-b
Advanced Features
FFI (Foreign Function Interface)
import { dlopen, FFIType, suffix } from "bun:ffi";
// Load native library
const lib = dlopen(`libsqlite3.${suffix}`, {
sqlite3_libversion: {
returns: FFIType.cstring,
args: [],
},
});
const version = lib.symbols.sqlite3_libversion();
console.log(version);
SQLite (Built-in)
import { Database } from "bun:sqlite";
// Open database
const db = new Database("mydb.sqlite");
// Create table
db.run(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY,
name TEXT
)
`);
// Insert
db.run("INSERT INTO users (name) VALUES (?)", ["Alice"]);
// Query
const users = db.query("SELECT * FROM users").all();
console.log(users);
// Prepared statements
const insert = db.prepare("INSERT INTO users (name) VALUES (?)");
insert.run("Bob");
insert.run("Charlie");
// Close
db.close();
TCP Sockets
// TCP server
const server = Bun.listen({
hostname: "localhost",
port: 8080,
socket: {
data(socket, data) {
socket.write(data); // Echo
},
open(socket) {
console.log("Connected");
},
close(socket) {
console.log("Disconnected");
},
},
});
// TCP client
const socket = await Bun.connect({
hostname: "localhost",
port: 8080,
socket: {
data(socket, data) {
console.log("Received:", data);
},
},
});
socket.write("Hello Server!");
Transpiler
// Transpile TypeScript to JavaScript
const transpiler = new Bun.Transpiler({
loader: "tsx",
});
const code = transpiler.transformSync(`
const x: number = 42;
console.log(x);
`);
console.log(code);
// const x = 42;
// console.log(x);
CLI Commands
Help & Info
# Version
bun --version
bun -v
# Help
bun --help
bun run --help
# Completions
bun completions
# Upgrade
bun upgrade
Project Commands
# Initialize project
bun init
# Run script
bun run start
bun start # short form
# Execute file
bun index.ts
# REPL
bun repl
Build Commands
# Bundle
bun build ./index.ts --outdir ./dist
# Watch
bun build --watch ./index.ts
# Minify
bun build --minify ./index.ts
# Target
bun build --target=browser ./index.ts
bun build --target=node ./index.ts
bun build --target=bun ./index.ts
Package Commands
# Install
bun install
bun i # short form
# Add
bun add <package>
bun add -d <package> # dev
bun add -g <package> # global
# Remove
bun remove <package>
# Update
bun update
bun update <package>
# Link
bun link
bun link <package>
Test Commands
# Run tests
bun test
# Watch mode
bun test --watch
# Specific file
bun test file.test.ts
# Coverage
bun test --coverage
# Bail on failure
bun test --bail
Debugging
Debug Mode
# Enable debug logging
bun --debug index.ts
# Verbose logging
bun --verbose index.ts
# Log level
bun --log-level=debug index.ts
Node Inspector
# Start with inspector
bun --inspect index.ts
# With breakpoint
bun --inspect-brk index.ts
# Custom port
bun --inspect=9229 index.ts
Environment Variables
# Debug Bun itself
BUN_DEBUG_QUIET_LOGS=1 bun run index.ts
# Disable cache
BUN_DISABLE_CACHE=1 bun install
# Force IPv4
BUN_FORCE_IPV4=1 bun run server.ts
Error Handling
// Stack traces include source maps
try {
throw new Error("Oops");
} catch (error) {
console.error(error);
// Points to TypeScript line numbers
}
// Unhandled promise rejections
process.on("unhandledRejection", (reason) => {
console.error("Unhandled:", reason);
});
Docker
Dockerfile
# Official Bun image
FROM oven/bun:1
# Set working directory
WORKDIR /app
# Copy package files
COPY package.json bun.lockb ./
# Install dependencies
RUN bun install --frozen-lockfile
# Copy source
COPY . .
# Build if needed
RUN bun run build
# Expose port
EXPOSE 3000
# Start app
CMD ["bun", "run", "start"]
Multi-stage Build
# Build stage
FROM oven/bun:1 AS builder
WORKDIR /app
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile
COPY . .
RUN bun run build
# Production stage
FROM oven/bun:1-slim
WORKDIR /app
COPY /app/dist ./dist
COPY /app/node_modules ./node_modules
COPY package.json ./
EXPOSE 3000
CMD ["bun", "run", "start"]
Docker Compose
version: "3.8"
services:
app:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
volumes:
- .:/app
- /app/node_modules
command: bun --watch index.ts
Migration from Node.js
Step-by-Step Guide
# 1. Install Bun
curl -fsSL https://bun.sh/install | bash
# 2. Remove old dependencies
rm -rf node_modules package-lock.json
# 3. Install with Bun
bun install
# 4. Update scripts in package.json
# Change: "start": "node index.js"
# To: "start": "bun index.js"
# 5. Test your app
bun run dev
bun test
# 6. Optional: Migrate to TypeScript
mv index.js index.ts
# Bun runs TypeScript directly!
Common Replacements
// __dirname and __filename (ESM)
// Node.js
import { fileURLToPath } from "url";
const __dirname = dirname(fileURLToPath(import.meta.url));
// Bun
const __dirname = import.meta.dir;
const __filename = import.meta.path;
// require (ESM)
// Node.js (need to use import)
// Bun
const data = require("./data.json"); // Works in ESM
// Environment variables
// Node.js
require("dotenv").config();
// Bun (automatic)
// Just use Bun.env or process.env
Performance Checklist
- Replace
fs.readFilewithBun.file().text() - Replace
http.createServerwithBun.serve() - Use
Bun.write()instead offs.writeFile - Remove dotenv package (built-in)
- Remove nodemon (use
bun --watch) - Replace Jest with
bun test - Use built-in bundler instead of Webpack
- Use
bunxinstead ofnpx
Common Patterns
REST API
const server = Bun.serve({
port: 3000,
async fetch(req) {
const url = new URL(req.url);
// GET /users
if (url.pathname === "/users" && req.method === "GET") {
return Response.json(users);
}
// POST /users
if (url.pathname === "/users" && req.method === "POST") {
const body = await req.json();
users.push(body);
return Response.json(body, { status: 201 });
}
// PUT /users/:id
if (url.pathname.match(/^\/users\/\d+$/) && req.method === "PUT") {
const id = url.pathname.split("/")[2];
const body = await req.json();
// Update user
return Response.json(body);
}
// DELETE /users/:id
if (url.pathname.match(/^\/users\/\d+$/) && req.method === "DELETE") {
const id = url.pathname.split("/")[2];
// Delete user
return new Response(null, { status: 204 });
}
return new Response("Not Found", { status: 404 });
},
});
File Upload
Bun.serve({
async fetch(req) {
if (req.method === "POST" && req.url.endsWith("/upload")) {
const formData = await req.formData();
const file = formData.get("file") as File;
if (!file) {
return new Response("No file", { status: 400 });
}
// Save file
await Bun.write(`uploads/${file.name}`, file);
return Response.json({
name: file.name,
size: file.size,
type: file.type,
});
}
return new Response("Method not allowed", { status: 405 });
},
});
Static File Serving
const server = Bun.serve({
port: 3000,
async fetch(req) {
const url = new URL(req.url);
const filePath = `./public${url.pathname}`;
const file = Bun.file(filePath);
if (await file.exists()) {
return new Response(file);
}
// Serve index.html for SPA
if (url.pathname.startsWith("/app")) {
return new Response(Bun.file("./public/index.html"));
}
return new Response("Not Found", { status: 404 });
},
});
Middleware Pattern
type Handler = (req: Request) => Response | Promise<Response>;
type Middleware = (handler: Handler) => Handler;
// Logger middleware
const logger: Middleware = (handler) => {
return async (req) => {
console.log(`${req.method} ${req.url}`);
const start = performance.now();
const response = await handler(req);
const duration = performance.now() - start;
console.log(`${response.status} ${duration.toFixed(2)}ms`);
return response;
};
};
// Auth middleware
const auth: Middleware = (handler) => {
return async (req) => {
const token = req.headers.get("Authorization");
if (!token) {
return new Response("Unauthorized", { status: 401 });
}
return handler(req);
};
};
// Compose middleware
const compose = (...middlewares: Middleware[]) => {
return (handler: Handler): Handler => {
return middlewares.reduceRight((h, m) => m(h), handler);
};
};
// Use
const handler = compose(
logger,
auth,
)(async (req) => {
return Response.json({ message: "Protected" });
});
Bun.serve({ fetch: handler });
Gotchas
CommonJS in ESM
Bun allows require() in ESM files, but this is non-standard:
// Works in Bun, breaks in Node.js
import { foo } from "./module.ts";
const bar = require("./legacy.js");
// Prefer standard ESM
import bar from "./legacy.js";
Top-level await
// Works in Bun (no need for async wrapper)
const data = await fetch("/api").then((r) => r.json());
// Node.js requires package.json type: "module"
Path resolution
// Bun resolves .ts extensions
import { func } from "./module.ts"; // OK in Bun
// Node.js requires:
import { func } from "./module.js"; // Even for .ts files
Global APIs
// Bun adds global APIs not in Node.js
console.log(Bun.version); // Bun only
// Use feature detection for compatibility
if (typeof Bun !== "undefined") {
console.log("Running in Bun");
}
Package compatibility
Some npm packages may not work due to:
- Native modules compiled for Node.js
- Reliance on Node.js internals
- Unsupported APIs
Check compatibility at bun.sh/guides/ecosystem
Hot reloading caveats
// Hot reload clears module cache but not memory
let cache = {}; // This persists across hot reloads
// Use --watch for full restarts
bun --watch index.ts
Also see
- Bun Documentation - Official comprehensive guide
- Bun GitHub - Source code and issues
- Bun Examples - Official code examples
- Bun Discord - Community support
- Bun Guides - Migration guides and tutorials
- Bun API Reference - Complete API documentation
- Bun vs Node.js - Performance comparisons