Bun¶
Bun is an all-in-one JavaScript runtime, bundler, transpiler, and package manager. Built in Zig with JavaScriptCore (Safari's engine), it's designed for speed and offers significant performance improvements over Node.js for many workloads.
Installation¶
macOS (Homebrew)¶
Linux/macOS (Shell)¶
Upgrade¶
Quick Start¶
# Run a script
bun run script.ts
bun script.ts # Shorthand
# Run package.json script
bun run dev
bun dev # Shorthand
# Install packages
bun install
bun add express
bun add -d typescript # Dev dependency
# Start REPL
bun repl
Package Management¶
Bun is a drop-in replacement for npm/yarn/pnpm with dramatic speed improvements.
Install Packages¶
# Install all dependencies
bun install
bun i # Shorthand
# Add dependencies
bun add express zod
bun add -d typescript vitest # Dev dependencies
bun add -g typescript # Global
# Add specific version
bun add react@18.2.0
bun add 'react@^18.0.0'
# Add from git
bun add github:user/repo
bun add github:user/repo#branch
# Install without running postinstall scripts
bun install --ignore-scripts
Remove Packages¶
Update Packages¶
List Packages¶
Cache Management¶
Lockfile¶
Bun uses bun.lockb (binary lockfile) for fast parsing:
Running Code¶
Run Files¶
bun run index.ts # TypeScript
bun run index.js # JavaScript
bun run index.jsx # JSX
bun index.ts # Shorthand
# With arguments
bun run script.ts -- --port 3000
Run package.json Scripts¶
bun run dev # Run "dev" script
bun dev # Shorthand (if no file named "dev")
bun run build
bun run test
Execute Packages¶
# Run package binary (like npx)
bunx cowsay "Hello"
bunx create-react-app my-app
bunx vitest run
# Without bunx (if globally installed)
bun x cowsay "Hello"
Project Setup¶
Initialize Project¶
package.json¶
{
"name": "my-bun-project",
"version": "1.0.0",
"module": "src/index.ts",
"type": "module",
"scripts": {
"dev": "bun --watch src/index.ts",
"start": "bun src/index.ts",
"build": "bun build src/index.ts --outdir dist",
"test": "bun test"
},
"dependencies": {
"hono": "^4.0.0"
},
"devDependencies": {
"@types/bun": "latest",
"typescript": "^5.3.0"
}
}
bunfig.toml¶
Optional configuration file:
# bunfig.toml
# Package management
[install]
# Where to install packages
optional = true
dev = true
peer = false
# Scoped registry
[install.scopes]
"@myorg" = "https://npm.myorg.com/"
# Environment variables for scripts
[run]
shell = "/bin/bash"
# Development server
[serve]
port = 3000
# Test configuration
[test]
coverage = true
coverageDir = "coverage"
# Bundler configuration
[build]
target = "browser"
minify = true
HTTP Server¶
Using Bun.serve()¶
// src/server.ts
const server = Bun.serve({
port: 3000,
fetch(request) {
const url = new URL(request.url);
if (url.pathname === "/") {
return new Response("Hello, World!");
}
if (url.pathname === "/api/data") {
return Response.json({ message: "Hello", timestamp: Date.now() });
}
if (url.pathname === "/api/echo" && request.method === "POST") {
return request.json().then(body =>
Response.json({ received: body })
);
}
return new Response("Not Found", { status: 404 });
},
});
console.log(`Server running at http://localhost:${server.port}`);
Using Hono Framework¶
// src/server.ts
import { Hono } from "hono";
import { cors } from "hono/cors";
import { logger } from "hono/logger";
const app = new Hono();
// Middleware
app.use("*", logger());
app.use("/api/*", cors());
// Routes
app.get("/", (c) => c.text("Hello, World!"));
app.get("/api/users/:id", (c) => {
const id = c.req.param("id");
return c.json({ id, name: `User ${id}` });
});
app.post("/api/users", async (c) => {
const body = await c.req.json();
return c.json({ created: true, ...body });
});
// Error handling
app.onError((err, c) => {
console.error(err);
return c.json({ error: "Internal Server Error" }, 500);
});
export default app;
WebSocket¶
const server = Bun.serve({
port: 3000,
fetch(req, server) {
// Upgrade to WebSocket
if (server.upgrade(req)) {
return; // Upgraded
}
return new Response("Not a WebSocket request", { status: 400 });
},
websocket: {
open(ws) {
console.log("Client connected");
ws.send("Welcome!");
},
message(ws, message) {
console.log("Received:", message);
ws.send(`Echo: ${message}`);
},
close(ws) {
console.log("Client disconnected");
},
},
});
File I/O¶
Read Files¶
// Read as text
const text = await Bun.file("file.txt").text();
// Read as JSON
const data = await Bun.file("data.json").json();
// Read as ArrayBuffer
const buffer = await Bun.file("image.png").arrayBuffer();
// Read as stream
const stream = Bun.file("large.txt").stream();
// File info
const file = Bun.file("file.txt");
console.log(file.size); // Size in bytes
console.log(file.type); // MIME type
Write Files¶
// Write text
await Bun.write("output.txt", "Hello, World!");
// Write JSON
await Bun.write("data.json", JSON.stringify({ key: "value" }));
// Write from Response
const response = await fetch("https://example.com/data.json");
await Bun.write("downloaded.json", response);
// Write from another file
await Bun.write("copy.txt", Bun.file("original.txt"));
File Watching¶
const watcher = Bun.spawn({
cmd: ["bun", "--watch", "src/index.ts"],
});
// Or programmatically
import { watch } from "fs";
watch("./src", { recursive: true }, (event, filename) => {
console.log(`${event}: ${filename}`);
});
Bundler¶
Bundle for Browser¶
# CLI
bun build src/index.ts --outdir dist --target browser --minify
# With source maps
bun build src/index.ts --outdir dist --sourcemap
Bundle API¶
const result = await Bun.build({
entrypoints: ["./src/index.ts"],
outdir: "./dist",
target: "browser",
minify: true,
sourcemap: "external",
splitting: true, // Code splitting
format: "esm", // or "cjs"
naming: "[name].[hash].[ext]",
external: ["react", "react-dom"],
define: {
"process.env.NODE_ENV": JSON.stringify("production"),
},
});
if (!result.success) {
for (const message of result.logs) {
console.error(message);
}
}
Compile to Executable¶
Testing¶
Write Tests¶
// src/math.test.ts
import { describe, expect, test, beforeEach, mock } from "bun:test";
import { add, divide } from "./math";
test("add function", () => {
expect(add(2, 3)).toBe(5);
expect(add(-1, 1)).toBe(0);
});
describe("divide", () => {
test("divides two numbers", () => {
expect(divide(10, 2)).toBe(5);
});
test("throws on division by zero", () => {
expect(() => divide(10, 0)).toThrow("Cannot divide by zero");
});
});
// Async tests
test("async operation", async () => {
const result = await fetchData();
expect(result.status).toBe("ok");
});
// Mocking
const mockFn = mock(() => "mocked");
test("mock function", () => {
mockFn();
expect(mockFn).toHaveBeenCalled();
expect(mockFn.mock.calls.length).toBe(1);
});
// Snapshot testing
test("snapshot", () => {
const obj = { name: "test", items: [1, 2, 3] };
expect(obj).toMatchSnapshot();
});
Run Tests¶
bun test # Run all tests
bun test src/math.test.ts # Specific file
bun test --watch # Watch mode
bun test --coverage # With coverage
bun test --timeout 10000 # Custom timeout
bun test --bail # Stop on first failure
Test Configuration¶
In bunfig.toml:
[test]
preload = ["./src/test-setup.ts"]
coverage = true
coverageDir = "coverage"
coverageReporters = ["text", "lcov"]
coverageThreshold = { line = 80, function = 80, branch = 80 }
Environment Variables¶
// Read environment variables
const apiKey = Bun.env.API_KEY;
const port = Bun.env.PORT ?? "3000";
// Or using process.env (Node.js compatible)
const dbUrl = process.env.DATABASE_URL;
.env Files¶
Bun automatically loads .env files:
SQLite (Built-in)¶
import { Database } from "bun:sqlite";
// Open database
const db = new Database("mydb.sqlite");
const db = new Database(":memory:"); // In-memory
// Execute queries
db.run("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)");
db.run("INSERT INTO users (name) VALUES (?)", ["Alice"]);
// Query data
const users = db.query("SELECT * FROM users").all();
const user = db.query("SELECT * FROM users WHERE id = ?").get(1);
// Prepared statements
const stmt = db.prepare("INSERT INTO users (name) VALUES (?)");
stmt.run("Bob");
stmt.run("Charlie");
// Transactions
const insertMany = db.transaction((users: string[]) => {
for (const name of users) {
db.run("INSERT INTO users (name) VALUES (?)", [name]);
}
});
insertMany(["Dave", "Eve"]);
// Close
db.close();
Shell Commands¶
import { $ } from "bun";
// Run command
const result = await $`ls -la`.text();
// With variables (escaped automatically)
const filename = "my file.txt";
await $`cat ${filename}`;
// Get output
const output = await $`echo "Hello"`.text();
const lines = await $`ls`.lines();
const buffer = await $`cat file.bin`.arrayBuffer();
// Check exit code
const { exitCode } = await $`test -f file.txt`.nothrow();
// Pipe commands
await $`cat file.txt | grep "pattern" | wc -l`;
// Environment variables
await $`echo $HOME`.env({ HOME: "/custom/home" });
Node.js Compatibility¶
Bun aims for Node.js compatibility:
// Node.js built-in modules work
import fs from "node:fs";
import path from "node:path";
import { EventEmitter } from "node:events";
// npm packages work
import express from "express";
import lodash from "lodash";
// process global
console.log(process.version);
console.log(process.platform);
console.log(process.cwd());
Compatibility Notes¶
Most npm packages work, but some Node.js APIs are not yet implemented:
vmmodule (partial)worker_threads(partial)cluster(partial)- Some
cryptofunctions
Check compatibility: https://bun.sh/docs/runtime/nodejs-apis
Performance Comparison¶
| Operation | Bun | Node.js |
|---|---|---|
| Install packages | ~25x faster | Baseline |
| Start time | ~4x faster | Baseline |
| TypeScript | Built-in | Requires setup |
| Bundling | Built-in | Requires esbuild/webpack |
| SQLite | Built-in | Requires better-sqlite3 |
| Hot reload | --watch | Requires nodemon |
Bun vs Node.js vs Deno¶
| Feature | Bun | Node.js | Deno |
|---|---|---|---|
| Engine | JavaScriptCore | V8 | V8 |
| TypeScript | Built-in | External | Built-in |
| Package manager | Built-in | npm/yarn | Import maps |
| Bundler | Built-in | External | Built-in |
| Test runner | Built-in | External | Built-in |
| SQLite | Built-in | External | External |
| Security model | None | None | Permissions |
| npm compatibility | High | Native | High |
| Speed | Very fast | Fast | Fast |
Common Workflows¶
Development¶
# Start with hot reload
bun --watch src/index.ts
# Or in package.json
{
"scripts": {
"dev": "bun --watch src/index.ts"
}
}
Production Build¶
# Bundle and minify
bun build src/index.ts --outdir dist --minify --target bun
# Or compile to standalone
bun build src/index.ts --compile --outfile dist/server
Docker¶
FROM oven/bun:1
WORKDIR /app
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile
COPY . .
CMD ["bun", "src/index.ts"]
Related Tools¶
- Node.js - Traditional JavaScript runtime
- Deno - Secure TypeScript runtime
- TypeScript - Typed JavaScript