
Bun
- 72 installs
- 22 repo stars
- Updated August 1, 2026
- itechmeat/llm-code
Use Bun as an all-in-one JS/TS runtime and toolkit for running code, managing packages, bundling, testing, and Bun-specific APIs.
About
Covers the Bun runtime, package manager, bundler, test runner, HTTP server, and built-in APIs like Bun.serve, bun:sqlite, and file I/O. A developer uses it when building or running JavaScript/TypeScript projects on Bun.
- Runtime, package manager, bundler, and test runner in one toolkit
- Built-in APIs for HTTP server, WebSockets, SQLite, S3, and Redis
Bun by the numbers
- 72 all-time installs (skills.sh)
- Ranked #3,076 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/itechmeat/llm-code --skill bunAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 72 |
|---|---|
| repo stars | ★ 22 |
| Last updated | August 1, 2026 |
| Repository | itechmeat/llm-code ↗ |
What it does
Use Bun as an all-in-one JS/TS runtime and toolkit for running code, managing packages, bundling, testing, and Bun-specific APIs.
Files
Bun
All-in-one JavaScript/TypeScript toolkit: runtime, package manager, test runner, bundler.
Quick Navigation
| Topic | Reference |
|---|---|
| Package Manager | references/package-manager.md |
| Project Setup | references/project-scaffolding.md |
| Development | references/development.md |
| Module System | references/module-system.md |
| TypeScript & JSX | references/typescript-jsx.md |
| Configuration | references/bunfig.md |
| HTTP Server | references/http-server.md |
| Browser Automation | references/webview.md |
| WebSockets | references/websockets.md |
| File I/O | references/file-io.md |
| SQLite | references/sqlite.md |
| S3 Storage | references/s3.md |
| Redis | references/redis.md |
| Low-Level Network | references/networking-low-level.md |
| Fetch API | references/fetch.md |
| Shell Scripts | references/shell.md |
| Spawn Process | references/spawn.md |
| Workers | references/workers.md |
| Native FFI | references/native-interop.md |
| C/C++ Compile | references/cc.md |
| Transpiler | references/transpiler.md |
| Plugins | references/plugins.md |
| FS Router | references/file-system-router.md |
| Environment Vars | references/env.md |
| Utilities | references/utilities.md |
| Node.js Compat | references/nodejs-compat.md |
When to Use Bun
- Running TypeScript/JSX without build step
- Fast HTTP server with native routing
- Headless browser automation with native input events
- SQLite database (embedded, no deps)
- WebSocket server/client
- S3-compatible storage (AWS, R2, MinIO)
- Redis caching/pub-sub
- Cross-platform shell scripts
- In-process cron scheduling
- Markdown parsing (v1.3.8+)
- Native library calls via FFI
Core Advantages
- 4x faster startup than Node.js
- Native TypeScript/JSX — no tsconfig needed
- ESM + CommonJS — both work seamlessly
- Web APIs built-in — fetch, WebSocket, etc.
- 30x faster installs than npm
Quick Start
# Run TypeScript directly
bun run index.ts
# Install packages
bun install
# Run package.json script
bun run dev
# Execute package binary
bunx cowsay "Hello"
# Run tests
bun test
# Build for production
bun build ./index.ts --outdir ./dist
# Bundle analysis for LLMs (v1.3.8+)
bun build ./index.ts --metafile-md --outdir ./distCritical Rules
| Don't | Do |
|---|---|
http.createServer() | Bun.serve() |
fs.readFileSync() | Bun.file().text() |
better-sqlite3 | bun:sqlite |
child_process.exec() | Bun.$ or Bun.spawn() |
dotenv | Built-in .env support |
Release Highlights (1.3.14)
- `Bun.Image`: built-in image decoding, transforms, and encoding for common formats with no npm dependency or native addon build step.
- Test workflow: the
1.3.13line improves dependency-aware filtering for changed-file test runs, which matters when you rely on partial local verification. - Patch-line runtime work:
1.3.13-1.3.14continues compatibility and performance work on top of the1.3.12WebView/cron/Markdown release line.
Release Highlights (1.3.12)
- `Bun.WebView`: native headless browser automation with WebKit on macOS and Chrome/Chromium via CDP on all platforms.
- `Bun.cron()` callback mode: in-process scheduler with no-overlap execution, UTC semantics, hot-reload cleanup, and
Disposablejob handles. - Markdown in terminal:
bun ./file.mdandBun.markdown.ansi()make terminal-native rendering a first-class workflow. - Networking/runtime: UDP error/truncation handling, Node-compatible unix-socket lifecycle, proxy tunnel reuse, and
Bun.serve()accept/perf improvements.
Essential Recipes
HTTP Server
Bun.serve({
port: 3000,
fetch(req) {
const url = new URL(req.url);
if (url.pathname === "/api/data") {
return Response.json({ ok: true });
}
return new Response("Not Found", { status: 404 });
},
});File Operations
// Read
const content = await Bun.file("data.txt").text();
// Write
await Bun.write("output.txt", "Hello World");
// JSON
const config = await Bun.file("config.json").json();SQLite
import { Database } from "bun:sqlite";
const db = new Database("app.db");
db.run("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)");
const insert = db.prepare("INSERT INTO users (name) VALUES (?)");
insert.run("Alice");
const users = db.query("SELECT * FROM users").all();WebSocket Server
Bun.serve({
fetch(req, server) {
if (server.upgrade(req)) return;
return new Response("Upgrade failed", { status: 400 });
},
websocket: {
message(ws, message) {
ws.send(`Echo: ${message}`);
},
},
});Shell Commands
import { $ } from "bun";
// Simple command
const files = await $`ls -la`.text();
// With variables (auto-escaped)
const name = "my file.txt";
await $`cat ${name}`;
// Piping
await $`cat data.csv | grep "pattern" | wc -l`;S3 Storage
import { s3 } from "bun";
// Upload
await s3.file("uploads/doc.pdf").write(data);
// Download
const content = await s3.file("uploads/doc.pdf").text();
// Presigned URL
const url = s3.presign("uploads/doc.pdf", { expiresIn: 3600 });Redis
import { redis } from "bun";
await redis.set("key", "value");
const value = await redis.get("key");
await redis.expire("key", 3600);Testing
import { expect, test, describe } from "bun:test";
describe("math", () => {
test("2 + 2 = 4", () => {
expect(2 + 2).toBe(4);
});
});Configuration (bunfig.toml)
[run]
watch = true
[install]
registry = "https://registry.npmjs.org"
[test]
coverage = trueEnvironment Variables
# .env files loaded automatically
DATABASE_URL=postgres://localhost/mydb// Access
Bun.env.DATABASE_URL;
process.env.DATABASE_URL;
import.meta.env.DATABASE_URL;Links
bunfig.toml
Configuration file for Bun. Place in project root or globally at ~/.bunfig.toml.
Location Priority
1. ./bunfig.toml (project) 2. $HOME/.bunfig.toml (global) 3. $XDG_CONFIG_HOME/.bunfig.toml (global)
Local overrides global; CLI flags override both.
Runtime Settings
# Preload scripts (plugins, setup)
preload = ["./setup.ts"]
# JSX configuration
jsx = "react"
jsxFactory = "h"
jsxFragment = "Fragment"
jsxImportSource = "react"
# Reduce memory (slower)
smol = true
# Log level
logLevel = "debug" # "debug" | "warn" | "error"
# Disable telemetry
telemetry = false
# Disable .env loading
env = false
# Console depth for object inspection
[console]
depth = 3Define (Replace Globals)
[define]
"process.env.API_URL" = "'https://api.example.com'"Custom Loaders
[loader]
".txt" = "text"
".csv" = "text"
".wasm" = "wasm"Available: jsx, js, ts, tsx, css, file, json, toml, wasm, napi, base64, dataurl, text
Test Runner
[test]
root = "./__tests__"
preload = ["./test-setup.ts"]
coverage = true
coverageThreshold = 0.9
coverageReporter = ["text", "lcov"]
coverageDir = "coverage"
randomize = true
rerunEach = 3
onlyFailures = true
[test.reporter]
dots = true
junit = "test-results.xml"Package Manager (bun install)
[install]
optional = true
dev = true
peer = true
production = false
exact = false
frozenLockfile = false
saveTextLockfile = true # bun.lock vs bun.lockb
auto = "auto" # "auto" | "force" | "disable" | "fallback"
linker = "isolated" # "isolated" | "hoisted"
# Directories
globalDir = "~/.bun/install/global"
globalBinDir = "~/.bun/bin"
# Registry
registry = "https://registry.npmjs.org"
# Or with auth:
registry = { url = "https://registry.npmjs.org", token = "xxx" }
# Scoped registries
[install.scopes]
myorg = { url = "https://registry.myorg.com/", token = "$NPM_TOKEN" }
# Cache
[install.cache]
dir = "~/.bun/install/cache"
disable = false
# Lockfile
[install.lockfile]
save = true
print = "yarn" # Generate yarn.lock alongside bun.lockbun run Settings
[run]
shell = "bun" # "bun" | "system"
bun = true # Auto-alias node to bun
silent = true # Suppress command outputDebug
[debug]
editor = "code" # "code" | "subl" | "nvim" | "vim" | "emacs" | "idea"HTTP Server (Bun.serve)
[serve.static]
plugins = ["bun-plugin-tailwind"]Bun C Compiler
Compile and run C code directly from JavaScript using TinyCC.
Basic Usage
import { cc } from "bun:ffi";
import source from "./hello.c" with { type: "file" };
const {
symbols: { hello },
} = cc({
source,
symbols: {
hello: {
args: [],
returns: "int",
},
},
});
console.log(hello()); // 42hello.c:
int hello() {
return 42;
}Supported Types
Same FFI types as dlopen:
| Type | C Type | Aliases |
|---|---|---|
i8 | int8_t | int8_t |
i16 | int16_t | int16_t |
i32 | int32_t | int32_t, int |
i64 | int64_t | int64_t |
u8 | uint8_t | uint8_t |
u16 | uint16_t | uint16_t |
u32 | uint32_t | uint32_t |
u64 | uint64_t | uint64_t |
f32 | float | float |
f64 | double | double |
bool | bool | — |
ptr | void\* | pointer |
cstring | char\* | — |
function | fn ptr | fn, callback |
napi_env | napi_env | — |
napi_value | napi_value | — |
N-API Integration
Use napi_value for complex JavaScript types:
import { cc } from "bun:ffi";
import source from "./hello.c" with { type: "file" };
const {
symbols: { hello },
} = cc({
source,
symbols: {
hello: {
args: ["napi_env"],
returns: "napi_value",
},
},
});
const result = hello(); // JavaScript stringhello.c:
#include <node/node_api.h>
napi_value hello(napi_env env) {
napi_value result;
napi_create_string_utf8(env, "Hello from C!", NAPI_AUTO_LENGTH, &result);
return result;
}Returning Objects
#include <node/node_api.h>
napi_value create_object(napi_env env) {
napi_value result;
napi_create_object(env, &result);
napi_value name;
napi_create_string_utf8(env, "John", NAPI_AUTO_LENGTH, &name);
napi_set_named_property(env, result, "name", name);
return result;
}cc() Options
source
File path to C code:
cc({
source: "hello.c",
// or
source: new URL("./hello.c", import.meta.url),
// or
source: Bun.file("hello.c"),
});symbols
Functions to expose:
cc({
source: "math.c",
symbols: {
add: {
args: ["i32", "i32"],
returns: "i32",
},
multiply: {
args: ["f64", "f64"],
returns: "f64",
},
},
});library
Link external libraries:
cc({
source: "db.c",
library: ["sqlite3"],
symbols: {
query: {
args: ["cstring"],
returns: "ptr",
},
},
});flags
Compiler flags:
cc({
source: "app.c",
flags: ["-I/usr/local/include", "-O2"],
});define
Preprocessor definitions:
cc({
source: "app.c",
define: {
NDEBUG: "1",
VERSION: '"1.0.0"',
},
});Practical Examples
Simple Math
import { cc } from "bun:ffi";
const { symbols: { add, multiply } } = cc({
source: `
int add(int a, int b) { return a + b; }
double multiply(double a, double b) { return a * b; }
`,
symbols: {
add: { args: ["i32", "i32"], returns: "i32" },
multiply: { args: ["f64", "f64"], returns: "f64" },
},
});
console.log(add(1, 2)); // 3
console.log(multiply(2.5, 4)); // 10.0Using SQLite
import { cc } from "bun:ffi";
import source from "./db.c" with { type: "file" };
const { symbols: { get_version } } = cc({
source,
library: ["sqlite3"],
symbols: {
get_version: {
args: [],
returns: "cstring",
},
},
});
console.log(get_version());db.c:
#include <sqlite3.h>
const char* get_version() {
return sqlite3_libversion();
}Processing Arrays
import { cc, ptr } from "bun:ffi";
const { symbols: { sum_array } } = cc({
source: `
int sum_array(int* arr, int len) {
int sum = 0;
for (int i = 0; i < len; i++) {
sum += arr[i];
}
return sum;
}
`,
symbols: {
sum_array: {
args: ["ptr", "i32"],
returns: "i32",
},
},
});
const numbers = new Int32Array([1, 2, 3, 4, 5]);
console.log(sum_array(ptr(numbers), numbers.length)); // 15Notes
- Uses TinyCC (embedded compiler)
- Low overhead for type conversion
- Supports N-API for complex types
- Source can be inline string or file
````markdown
Development Workflow
Runtime, watch mode, debugging, and environment variables.
Running Code
bun run index.ts # Run file
bun index.ts # Same (shorthand)
bun run dev # Run package.json script
bun dev # Same (if no file "dev" exists)````
Lifecycle scripts auto-run:
bun install # Runs postinstall
bun add react # Runs postinstall for react---
Watch Mode
--watch (Restart)
Restarts entire process on file changes:
bun --watch index.ts
bun --watch run devbunfig.toml:
[run]
watch = true--hot (Hot Reload)
Preserves state, reloads modules in-place:
bun --hot index.ts// module-level state preserved across reloads
let count = globalThis.count ?? 0;
globalThis.count = count;
// HTTP handlers auto-reload
export default {
fetch() {
return new Response(`Count: ${++count}`);
},
};Differences:
| Feature | --watch | --hot |
|---|---|---|
| State | Reset | Preserved |
| Process | Restart | In-place |
| Speed | Slower | Faster |
| Use case | General dev | HTTP servers |
Bun.cron() in-process scheduler (v1.3.12)
Use in-process cron when the job should share memory, caches, DB pools, or module state with the current Bun process.
process.on("unhandledRejection", console.error);
using job = Bun.cron("*/5 * * * *", async function () {
await syncState();
});Operational rules:
- In-process cron uses UTC, not the host local timezone.
- Jobs never overlap; the next run is scheduled only after the current handler settles.
- Under
bun --hot, in-process cron jobs are cleared before module re-evaluation, so schedule edits do not leak duplicate timers. - Use
Bun.cron(path, schedule, title)only when you need OS-level persistence across restarts.
Test Workflow Notes (v1.3.13)
The 1.3.13 line improves dependency-aware test filtering for changed-file workflows. If you rely on partial local verification, re-test your assumptions about which dependent test files Bun includes instead of assuming older file-only matching behavior.
Keep these rules in mind:
bun teststill discovers files by naming conventions such as*.test.ts,*_test.ts,*.spec.ts, and*_spec.ts.- Positional filters remain simple path substring matches, not glob patterns.
- For exact files, prefer
bun test ./path/to/file.test.tsso Bun treats the argument as a path rather than a fuzzy filter.
---
Debugging
VS Code
// .vscode/launch.json
{
"version": "0.2.0",
"configurations": [
{
"type": "bun",
"request": "launch",
"name": "Debug Bun",
"program": "${workspaceFolder}/index.ts",
"cwd": "${workspaceFolder}"
}
]
}Install Bun for Visual Studio Code extension.
Inspector (Chrome DevTools)
bun --inspect index.ts # Listen on ws://localhost:6499
bun --inspect=0.0.0.0:9229 # Custom host:port
bun --inspect-brk index.ts # Break on first line
bun --inspect-wait index.ts # Wait for debuggerOpen chrome://inspect → Configure target.
Web Debugger
bun --inspect index.ts
# Open: https://debug.bun.sh/Inspector API
const inspector = Bun.inspector;
inspector.url; // WebSocket URL
inspector.open({ port: 9229 });
inspector.close();
// In-process breakpoint
Bun.inspect.break("reason");---
Performance
Bun is 4x faster startup than Node.js due to:
- Native TS/JSX transpilation (no build step)
- Native ESM support
- Optimized module resolution
- Hardware-accelerated I/O
Benchmarks:
time bun index.ts # ~6ms startup
time node index.js # ~25ms startup---
Key Points
bun runomits "run" if file doesn't exist--hotfor HTTP servers,--watchfor everything else.env.localhas highest priorityBun.envis typed,process.envfor compatibility- VS Code debugger requires Bun extension
--inspect-brkto break on first line
Bun Environment Variables
Automatic .env file support with multiple access methods.
Automatic Loading
Files loaded in order of precedence:
1. .env 2. .env.production, .env.development, .env.test (based on NODE_ENV) 3. .env.local
Reading Variables
// All equivalent
process.env.API_TOKEN;
Bun.env.API_TOKEN;
import.meta.env.API_TOKEN;Setting Variables
In .env File
FOO=hello
BAR=world
# Quotes supported
SINGLE='value'
DOUBLE="value"
BACKTICK=`value`Command Line
FOO=hello bun run devCross-Platform (Windows)
bun exec 'FOO=hello bun run dev'package.json Scripts
{
"scripts": {
"dev": "NODE_ENV=development bun --watch app.ts"
}
}Programmatic
process.env.FOO = "hello";Variable Expansion
DB_USER=postgres
DB_PASSWORD=secret
DB_HOST=localhost
DB_PORT=5432
DB_URL=postgres://$DB_USER:$DB_PASSWORD@$DB_HOST:$DB_PORT/mydbEscape to disable expansion:
BAR=hello\$FOO # Literal "$FOO"Manual .env Files
bun --env-file=.env.custom src/index.ts
bun --env-file=.env.1 --env-file=.env.2 run buildDisable .env Loading
bun --no-env-file index.tsOr in bunfig.toml:
env = falseTypeScript Typing
Default type: string | undefined
For autocompletion:
declare module "bun" {
interface Env {
API_TOKEN: string;
DATABASE_URL: string;
}
}Debug Variables
bun --print process.envBun-Specific Variables
| Variable | Description |
|---|---|
NODE_ENV | development, production, test |
NO_COLOR=1 | Disable ANSI colors |
FORCE_COLOR=1 | Force ANSI colors |
NODE_TLS_REJECT_UNAUTHORIZED=0 | Disable SSL validation |
BUN_CONFIG_VERBOSE_FETCH=curl | Log fetch requests |
BUN_CONFIG_MAX_HTTP_REQUESTS | Max concurrent fetches (default: 256) |
BUN_CONFIG_NO_CLEAR_TERMINAL_ON_RELOAD=true | Don't clear on watch reload |
DO_NOT_TRACK=1 | Disable crash reports/telemetry |
BUN_OPTIONS | Prepend CLI args (e.g., --hot) |
TMPDIR | Temp directory for bundling |
BUN_RUNTIME_TRANSPILER_CACHE_PATH | Transpiler cache location |
Transpiler Cache
Files >50KB are cached. Disable:
BUN_RUNTIME_TRANSPILER_CACHE_PATH=0 bun run devNo dotenv Required
Bun handles .env natively — no need for dotenv or dotenv-expand.
Practical Patterns
Environment-Specific Config
// .env
DATABASE_URL=postgres://localhost/myapp
// .env.production
DATABASE_URL=postgres://prod-server/myapp
// .env.local (gitignored, overrides all)
DATABASE_URL=postgres://my-local/myappType-Safe Config
// env.ts
declare module "bun" {
interface Env {
DATABASE_URL: string;
API_SECRET: string;
PORT: string;
}
}
export const config = {
databaseUrl: Bun.env.DATABASE_URL,
apiSecret: Bun.env.API_SECRET,
port: parseInt(Bun.env.PORT || "3000"),
};Validation
const required = ["DATABASE_URL", "API_SECRET"];
for (const key of required) {
if (!Bun.env[key]) {
throw new Error(`Missing required env var: ${key}`);
}
}Fetch
WHATWG fetch implementation with Bun-specific extensions.
Basic Usage
const response = await fetch("https://example.com");
console.log(response.status);
const data = await response.json();Request Methods
// GET (default)
await fetch("https://api.example.com/users");
// POST with body
await fetch("https://api.example.com/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "John" }),
});
// Using Request object
const req = new Request("https://api.example.com", {
method: "POST",
body: "Hello",
});
await fetch(req);Response Body
response.text(); // Promise<string>
response.json(); // Promise<any>
response.formData(); // Promise<FormData>
response.bytes(); // Promise<Uint8Array>
response.arrayBuffer(); // Promise<ArrayBuffer>
response.blob(); // Promise<Blob>Streaming
Response Streaming
for await (const chunk of response.body) {
console.log(chunk);
}
// Or with reader
const reader = response.body.getReader();
const { value, done } = await reader.read();Request Streaming
const stream = new ReadableStream({
start(controller) {
controller.enqueue("Hello");
controller.close();
},
});
await fetch(url, { method: "POST", body: stream });Timeout & Cancellation
// Timeout
await fetch(url, {
signal: AbortSignal.timeout(5000),
});
// Manual cancellation
const controller = new AbortController();
fetch(url, { signal: controller.signal });
controller.abort();Proxy
await fetch(url, {
proxy: "http://proxy.com:8080",
});
// With auth headers
await fetch(url, {
proxy: {
url: "http://proxy.com",
headers: { "Proxy-Authorization": "Bearer token" },
},
});As of v1.3.12, HTTPS requests sent through an HTTP proxy can reuse CONNECT tunnels for sequential requests to the same proxy/target pair instead of renegotiating the tunnel every time.
TLS Options
// Client certificate
await fetch("https://example.com", {
tls: {
key: Bun.file("/path/to/key.pem"),
cert: Bun.file("/path/to/cert.pem"),
},
});
// Disable TLS validation (dev only!)
await fetch("https://localhost:3000", {
tls: { rejectUnauthorized: false },
});Unix Domain Sockets
await fetch("http://localhost/api", {
unix: "/var/run/docker.sock",
});Protocol Support
// File URLs
await fetch("file:///path/to/file.txt");
// Data URLs
await fetch("data:text/plain;base64,SGVsbG8=");
// Blob URLs
const blob = new Blob(["Hello"]);
await fetch(URL.createObjectURL(blob));
// S3 URLs
await fetch("s3://bucket/key", {
s3: {
accessKeyId: "...",
secretAccessKey: "...",
region: "us-east-1",
},
});Debugging
await fetch(url, { verbose: true });
// Prints request/response headers
// Or as curl commands
await fetch(url, { verbose: "curl" });Environment variable:
BUN_CONFIG_VERBOSE_FETCH=true bun script.tsPerformance
DNS Prefetch
import { dns } from "bun";
dns.prefetch("api.example.com");Preconnect
fetch.preconnect("https://api.example.com");Or at startup:
bun --fetch-preconnect https://api.example.com script.tsProxy environment variables such as HTTP_PROXY, HTTPS_PROXY, and NO_PROXY now take effect on subsequent fetch() calls when changed at runtime, instead of being read only once at process startup.
Connection Limit
BUN_CONFIG_MAX_HTTP_REQUESTS=512 bun script.tsDefault: 256, Max: 65,336
Bun-Specific Options
await fetch(url, {
decompress: true, // Auto decompress gzip/br/zstd
keepalive: false, // Disable connection reuse
verbose: true, // Debug logging
proxy: "...", // HTTP proxy
unix: "...", // Unix socket
tls: { ... }, // TLS options
s3: { ... }, // S3 credentials
});Write Response to File
await Bun.write("output.txt", response);File I/O
Optimized APIs for reading and writing files.
Reading Files
Bun.file()
Creates lazy file reference (doesn't read immediately):
const file = Bun.file("data.txt");
// Metadata
file.size; // Bytes
file.type; // MIME type
await file.exists(); // BooleanRead Content
const file = Bun.file("data.txt");
await file.text(); // String
await file.json(); // Parsed JSON
await file.bytes(); // Uint8Array
await file.arrayBuffer(); // ArrayBuffer
await file.stream(); // ReadableStreamFile References
// Relative path
Bun.file("./data.txt");
// Absolute path
Bun.file("/tmp/data.txt");
// File descriptor
Bun.file(1234);
// URL
Bun.file(new URL("file:///path/to/file.txt"));
Bun.file(new URL(import.meta.url)); // Current file
// Custom MIME type
Bun.file("data.bin", { type: "application/octet-stream" });Standard Streams
Bun.stdin; // Read-only
Bun.stdout; // Write
Bun.stderr; // WriteWriting Files
Bun.write()
// String to file
await Bun.write("output.txt", "Hello World");
// Copy file
await Bun.write("output.txt", Bun.file("input.txt"));
// ArrayBuffer/TypedArray
await Bun.write("data.bin", new Uint8Array([1, 2, 3]));
// HTTP Response body
const res = await fetch("https://example.com");
await Bun.write("page.html", res);
// To stdout
await Bun.write(Bun.stdout, Bun.file("input.txt"));Returns: Promise<number> — bytes written.
Images (Bun.Image, v1.3.14)
Bun now has a built-in image pipeline for decode → transform → encode workflows with no extra npm package.
const out = await Bun.file("photo.jpg").image().resize(400, 400, { fit: "inside" }).webp({ quality: 80 }).write("thumb.webp");Key rules:
Bun.Imageis lazy and chainable; work begins only when you await a terminal such as.bytes(),.blob(), or.write().- Supported core formats include JPEG, PNG, and WebP everywhere; HEIC/AVIF support depends on platform backends.
- The input format is detected from bytes, not file extension or
Content-Type. - Do not pass unvalidated user-controlled path strings directly into the constructor; convert untrusted input to bytes first.
Useful operations include:
.metadata()for width/height/format without full decode.resize(),.rotate(),.flip(),.flop(),.modulate()for transforms.jpeg(),.png(),.webp(),.heic(),.avif()for output selection.placeholder()for low-quality image placeholders
Bun.Image pipelines also work well with Bun.serve() handlers, but prefer awaiting a terminal result before constructing the Response if you want the encode to stay off the JS thread.
Incremental Writing (FileSink)
const file = Bun.file("log.txt");
const writer = file.writer({ highWaterMark: 1024 * 1024 }); // 1MB buffer
writer.write("Line 1\n");
writer.write("Line 2\n");
writer.flush(); // Force write to disk
writer.end(); // Flush and close
// Process lifecycle
writer.unref(); // Allow process to exit
writer.ref(); // Keep process aliveDelete Files
await Bun.file("old.txt").delete();Directories (node:fs)
import { readdir, mkdir, rm } from "node:fs/promises";
// List files
const files = await readdir("./src");
// List recursively
const all = await readdir("./src", { recursive: true });
// Create directory
await mkdir("./logs", { recursive: true });
// Delete directory
await rm("./temp", { recursive: true, force: true });Performance Tips
Bun.write()uses optimal syscalls (copy_file_range,sendfile, etc.)BunFileis lazy — reading happens only when content methods called- Use
FileSinkfor incremental writes (streaming logs, etc.)
Example: cat Command
const path = process.argv.at(-1)!;
await Bun.write(Bun.stdout, Bun.file(path));2x faster than GNU cat on Linux.
API Reference
interface BunFile extends Blob {
size: number;
type: string;
text(): Promise<string>;
json(): Promise<any>;
bytes(): Promise<Uint8Array>;
arrayBuffer(): Promise<ArrayBuffer>;
stream(): ReadableStream;
exists(): Promise<boolean>;
delete(): Promise<void>;
writer(opts?: { highWaterMark?: number }): FileSink;
}
interface FileSink {
write(chunk: string | ArrayBufferView): number;
flush(): number | Promise<number>;
end(): number | Promise<number>;
ref(): void;
unref(): void;
}File System Router
Fast file-based route resolution API (primarily for framework authors).
Basic Usage
const router = new Bun.FileSystemRouter({
style: "nextjs",
dir: "./pages",
origin: "https://example.com",
assetPrefix: "_next/static/",
});Directory Structure
pages/
├── index.tsx → /
├── about.tsx → /about
├── blog/
│ ├── index.tsx → /blog
│ └── [slug].tsx → /blog/:slug
└── [[...catchall]].tsx → /*Match Routes
// String path
router.match("/");
// → { filePath: "/pages/index.tsx", kind: "exact", name: "/" }
// With params
router.match("/blog/hello-world");
// → { params: { slug: "hello-world" }, kind: "dynamic" }
// With query
router.match("/settings?theme=dark");
// → { query: { theme: "dark" } }
// Request object
router.match(new Request("https://example.com/blog/post"));Match Result
{
filePath: string; // Absolute file path
kind: "exact" | "dynamic" | "catch-all" | "optional-catch-all";
name: string; // Route pattern
pathname: string; // Matched URL path
src: string; // Full URL with origin + assetPrefix
params?: Record<string, string>; // URL params
query?: Record<string, string>; // Query string
}Route Patterns
| Pattern | Example | Matches |
|---|---|---|
index.tsx | / | Exact |
about.tsx | /about | Exact |
[id].tsx | /123 | Dynamic segment |
[...slug].tsx | /a/b/c | Catch-all (required) |
[[...slug]].tsx | / or /a/b | Optional catch-all |
Reload
Re-scan files when directory changes:
router.reload();Constructor Options
new Bun.FileSystemRouter({
dir: string; // Pages directory
style: "nextjs"; // Only supported style
origin?: string; // Base URL for src
assetPrefix?: string; // Prefix for static assets
fileExtensions?: string[]; // Allowed extensions
});Example: HTTP Server
const router = new Bun.FileSystemRouter({
style: "nextjs",
dir: "./pages",
});
Bun.serve({
async fetch(req) {
const match = router.match(req);
if (!match) {
return new Response("Not Found", { status: 404 });
}
const module = await import(match.filePath);
return module.default(req, match.params);
},
});Notes
- Next.js 13
appdirectory not yet supported - Only
"nextjs"style available currently - Reads directory on init, use
reload()to refresh
HTTP Server
Basic Server
const server = Bun.serve({
port: 3000,
fetch(req) {
return new Response("Hello!");
},
});
console.log(`Server running at ${server.url}`);Routes (Bun 1.2.3+)
Bun.serve({
routes: {
// Static response
"/": new Response("Home"),
// Dynamic handler
"/users/:id": (req) => {
return new Response(`User ${req.params.id}`);
},
// Per-method handlers
"/api/posts": {
GET: () => Response.json({ posts: [] }),
POST: async (req) => {
const body = await req.json();
return Response.json({ created: true, ...body });
},
},
// Wildcard
"/api/*": Response.json({ error: "Not found" }, { status: 404 }),
// Redirect
"/old": Response.redirect("/new"),
// Serve file
"/favicon.ico": Bun.file("./favicon.ico"),
},
// Fallback for unmatched
fetch(req) {
return new Response("Not Found", { status: 404 });
},
});HTML Imports
import app from "./index.html";
Bun.serve({
routes: {
"/": app,
},
});- Development (
bun --hot): On-demand bundling, HMR - Production (
bun build): Pre-built manifest
Configuration
Bun.serve({
port: 8080, // Default: $BUN_PORT, $PORT, 3000
hostname: "0.0.0.0", // Default: "0.0.0.0"
// port: 0, // Random available port
});Server Methods
// Stop server
await server.stop(); // Graceful (wait for requests)
await server.stop(true); // Force close all connections
// Hot reload handlers
server.reload({
routes: { "/": new Response("v2") },
fetch(req) {
return new Response("v2");
},
});
// Process lifecycle
server.unref(); // Don't keep process alive
server.ref(); // Keep process alive (default)Per-Request Controls
Bun.serve({
fetch(req, server) {
// Set timeout (seconds)
server.timeout(req, 60);
// Get client IP
const ip = server.requestIP(req);
// { address: "127.0.0.1", port: 54321, family: "IPv4" }
return new Response("OK");
},
});Metrics
server.pendingRequests; // Active HTTP requests
server.pendingWebSockets; // Active WebSocket connections
server.subscriberCount("topic"); // WebSocket subscribersError Handler
Bun.serve({
fetch(req) {
/* ... */
},
error(error) {
console.error(error);
return new Response("Server Error", { status: 500 });
},
});WebSocket Upgrade
Bun.serve({
fetch(req, server) {
if (req.headers.get("upgrade") === "websocket") {
const success = server.upgrade(req, {
data: { userId: "123" },
});
return success ? undefined : new Response("Upgrade failed", { status: 400 });
}
return new Response("Hello");
},
websocket: {
open(ws) {
console.log("Connected");
},
message(ws, msg) {
ws.send(`Echo: ${msg}`);
},
close(ws) {
console.log("Disconnected");
},
},
});Export Default Syntax
export default {
port: 3000,
fetch(req) {
return new Response("Hello");
},
} satisfies import("bun").Serve;REST API Example
import { Database } from "bun:sqlite";
const db = new Database("app.db");
Bun.serve({
routes: {
"/api/users": {
GET: () => Response.json(db.query("SELECT * FROM users").all()),
POST: async (req) => {
const { name } = await req.json();
const id = crypto.randomUUID();
db.run("INSERT INTO users (id, name) VALUES (?, ?)", [id, name]);
return Response.json({ id, name }, { status: 201 });
},
},
"/api/users/:id": (req) => {
const user = db.query("SELECT * FROM users WHERE id = ?").get(req.params.id);
return user ? Response.json(user) : new Response("Not Found", { status: 404 });
},
},
});Key Points
- Use
Bun.serve()nothttp.createServer() - Routes object for declarative routing
req.paramsfor URL parametersResponse.json()for JSON responses- WebSocket support built-in via
server.upgrade()
Runtime notes (v1.3.12)
- Linux
Bun.serve()now enablesTCP_DEFER_ACCEPT, which can reduce latency on busy HTTP listeners. - Async handlers that resume after
awaitno longer hit the same write-batching performance cliff under concurrency.
````markdown
Module System
Module resolution, auto-install, and file type handling.
Import Resolution
// Extension optional
import { hello } from "./hello"; // Tries .tsx, .ts, .js, etc.
import { hello } from "./hello.ts"; // Explicit
import { hello } from "./hello.js"; // Also resolves to .ts````
Resolution order: .tsx → .jsx → .ts → .mjs → .js → .cjs → .json → index.*
ES Modules vs CommonJS
// ESM (recommended)
import { foo } from "./foo";
export const bar = 1;
// CommonJS (supported)
const { foo } = require("./foo");
module.exports = { bar: 1 };
// Can mix
import { stuff } from "./module.cjs";
const other = require("./other");Package Resolution
import { z } from "zod";Bun scans up for node_modules/zod, reads package.json exports:
{
"exports": {
"bun": "./index.ts", // Bun-specific (can ship TS!)
"import": "./index.mjs", // ESM
"require": "./index.cjs", // CommonJS
"default": "./index.js"
}
}Path Aliases
tsconfig.json:
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"],
"@config": ["./config.ts"]
}
}
}import { db } from "@/db";
import config from "@config";package.json (subpath imports):
{
"imports": {
"#config": "./config.ts",
"#utils/*": "./src/utils/*"
}
}---
Auto-Install
When no node_modules exists, Bun auto-installs packages:
import { z } from "zod"; // Auto-installs latest
import { z } from "zod@3.22.0"; // Exact version
import { z } from "zod@^3.20"; // Semver rangeVersion resolution:
1. bun.lock (locked version) 2. package.json (specified range) 3. latest
Configure in bunfig.toml:
[install]
auto = "auto" # Default: auto if no node_modules
auto = "force" # Always auto-install
auto = "disable" # Never
auto = "fallback" # Check node_modules firstPortable script example:
#!/usr/bin/env bun
// No package.json needed!
import { Hono } from "hono@4.0.0";
const app = new Hono();
export default app;---
File Types & Loaders
| Extension | Loader | Returns |
|---|---|---|
.ts, .tsx | TypeScript | Module |
.js, .jsx | JavaScript | Module |
.json | JSON | Object |
.toml | TOML | Object |
.txt | Text | String |
.wasm | WebAssembly | Module |
.node | N-API | Native addon |
.db (with type) | SQLite | Database |
Import examples:
import pkg from "./package.json";
import config from "./bunfig.toml";
import readme from "./README.txt";
import db from "./my.db" with { type: "sqlite" };Custom loaders (bunfig.toml):
[loader]
".csv" = "text"
".graphql" = "text"
".mdx" = "jsx"Type declarations for custom extensions:
// global.d.ts
declare module "*.svg" {
const content: string;
export default content;
}---
Key Points
- No extension needed — Bun resolves
.ts,.tsx, etc. - ESM + CJS — can mix in same project
- `"bun"` condition — ship TypeScript to npm directly
- Auto-install — no
npm installfor quick scripts - Top-level await — supported in ESM
- TypeScript — transpiled, not type-checked
Bun Native Interop
Node-API and FFI for native code integration.
Node-API
Bun implements 95% of Node-API. Use .node files directly:
const napi = require("./my-module.node");Or with process.dlopen:
let mod = { exports: {} };
process.dlopen(mod, "./my-module.node");---
FFI (bun:ffi)
Experimental — call native libraries from JavaScript.
Works with any language supporting C ABI (C, C++, Rust, Zig, etc.).
Basic Example
import { dlopen, FFIType, suffix } from "bun:ffi";
// suffix = "dylib" | "so" | "dll"
const lib = dlopen(`libsqlite3.${suffix}`, {
sqlite3_libversion: {
args: [],
returns: FFIType.cstring,
},
});
console.log(lib.symbols.sqlite3_libversion());FFI Types
| FFIType | C Type | Aliases |
|---|---|---|
i8 | int8_t | int8_t |
i16 | int16_t | int16_t |
i32 | int32_t | int32_t, int |
i64 | int64_t | int64_t |
u8 | uint8_t | uint8_t |
u16 | uint16_t | uint16_t |
u32 | uint32_t | uint32_t |
u64 | uint64_t | uint64_t |
f32 | float | float |
f64 | double | double |
bool | bool | — |
char | char | — |
ptr | void\* | pointer |
cstring | char\* | — |
buffer | char\* | — |
function | fn pointer | fn, callback |
Compiling Native Code
Zig:
// add.zig
pub export fn add(a: i32, b: i32) i32 {
return a + b;
}zig build-lib add.zig -dynamic -OReleaseFastRust:
// add.rs
#[no_mangle]
pub extern "C" fn add(a: i32, b: i32) -> i32 {
a + b
}rustc --crate-type cdylib add.rsC++:
extern "C" int32_t add(int32_t a, int32_t b) {
return a + b;
}zig build-lib add.cpp -dynamic -lc -lc++Loading & Calling
import { dlopen, FFIType, suffix } from "bun:ffi";
const lib = dlopen(`libadd.${suffix}`, {
add: {
args: [FFIType.i32, FFIType.i32],
returns: FFIType.i32,
},
});
console.log(lib.symbols.add(1, 2)); // 3Strings
CString
import { CString } from "bun:ffi";
// From null-terminated pointer
const str = new CString(ptr);
// With known length
const str = new CString(ptr, 0, byteLength);
// Safe to use after ptr is freed (cloned)
lib.free(str.ptr);
console.log(str); // Still worksPointers
TypedArray to Pointer
import { ptr } from "bun:ffi";
const myArray = new Uint8Array(32);
const myPtr = ptr(myArray);Pointer to ArrayBuffer
import { toArrayBuffer } from "bun:ffi";
const buffer = toArrayBuffer(myPtr, 0, 32);
const array = new Uint8Array(buffer);Reading from Pointer
import { read } from "bun:ffi";
// Fast reading (no ArrayBuffer creation)
const value = read.u8(myPtr, 0); // byte at offset 0
const value2 = read.i32(myPtr, 4); // int32 at offset 4Read functions: read.ptr, read.i8, read.i16, read.i32, read.i64, read.u8, read.u16, read.u32, read.u64, read.f32, read.f64
Function Pointers
import { CFunction, linkSymbols } from "bun:ffi";
// Single function pointer
const getVersion = new CFunction({
returns: "cstring",
args: [],
ptr: myFunctionPointer,
});
getVersion();
// Multiple function pointers
const lib = linkSymbols({
getMajor: {
returns: "cstring",
args: [],
ptr: majorPtr,
},
getMinor: {
returns: "cstring",
args: [],
ptr: minorPtr,
},
});Callbacks (JSCallback)
import { JSCallback, CString } from "bun:ffi";
const callback = new JSCallback(
(ptr, length) => {
const str = new CString(ptr, 0, length);
return /pattern/.test(str);
},
{
returns: "bool",
args: ["ptr", "usize"],
threadsafe: false, // Set true for cross-thread calls
}
);
// Pass to native function
nativeSearch(data, callback);
// Performance: use .ptr directly
nativeSearch(data, callback.ptr);
// Clean up when done
callback.close();Memory Management
FFI does not manage memory. You must free it yourself.
JavaScript FinalizationRegistry
const registry = new FinalizationRegistry((ptr) => {
lib.free(ptr);
});
const buffer = new Uint8Array(toArrayBuffer(allocatedPtr, 0, size));
registry.register(buffer, allocatedPtr);Native Deallocator
import { toArrayBuffer } from "bun:ffi";
toArrayBuffer(
bytes,
0,
byteLength,
deallocatorContext, // Optional context pointer
deallocatorFunction, // Called when GC frees buffer
);Practical Patterns
SQLite Version
import { dlopen, FFIType, suffix } from "bun:ffi";
const sqlite = dlopen(`libsqlite3.${suffix}`, {
sqlite3_libversion: {
args: [],
returns: FFIType.cstring,
},
});
console.log(sqlite.symbols.sqlite3_libversion());Image Encoding
import { dlopen, ptr } from "bun:ffi";
const lib = dlopen("libpng.dylib", {
encode_png: {
args: ["ptr", "u32", "u32"],
returns: "ptr",
},
});
const pixels = new Uint8ClampedArray(128 * 128 * 4);
pixels.fill(254);
const outPtr = lib.symbols.encode_png(pixels, 128, 128);
const png = new Uint8Array(toArrayBuffer(outPtr));
await Bun.write("out.png", png);Calling System Libraries
import { dlopen, FFIType, suffix } from "bun:ffi";
const libc = dlopen(`libc.${suffix}`, {
getpid: {
args: [],
returns: FFIType.i32,
},
getenv: {
args: [FFIType.cstring],
returns: FFIType.cstring,
},
});
console.log("PID:", libc.symbols.getpid());
console.log("HOME:", libc.symbols.getenv("HOME"));Performance
bun:ffiis 2-6x faster than Node.js FFI via Node-API- Bun JIT-compiles C bindings using embedded TinyCC
- Use
callback.ptrdirectly for slight performance boost
Limitations
- Async functions not supported in callbacks
- Memory not managed automatically
- Windows HANDLE should use
u64, notptr - Thread-safe callbacks experimental
````markdown
Low-Level Networking
High-performance TCP and UDP APIs for custom protocols.
TCP
Server
const server = Bun.listen({
hostname: "localhost",
port: 8080,
socket: {
open(socket) {
console.log("Connected");
},
data(socket, data) {
socket.write(`Echo: ${data}`);
},
close(socket, error) {},
drain(socket) {}, // Socket ready for more data
error(socket, error) {},
},
});
server.stop(); // Keep existing connections
server.stop(true); // Close all
server.unref(); // Don't keep process alive````
Client
const socket = await Bun.connect({
hostname: "localhost",
port: 8080,
socket: {
open(socket) {
socket.write("Hello server!");
},
data(socket, data) {
console.log("Received:", data);
},
close(socket, error) {},
drain(socket) {},
error(socket, error) {},
connectError(socket, error) {}, // Connection failed
end(socket) {}, // Server closed
timeout(socket) {},
},
});Per-Socket Data
type SocketData = { sessionId: string };
Bun.listen<SocketData>({
hostname: "localhost",
port: 8080,
socket: {
open(socket) {
socket.data = { sessionId: crypto.randomUUID() };
},
data(socket, data) {
console.log(`${socket.data.sessionId}: ${data}`);
},
},
});TLS
// Server
Bun.listen({
port: 443,
socket: {
/* handlers */
},
tls: {
key: Bun.file("./key.pem"),
cert: Bun.file("./cert.pem"),
},
});
// Client
await Bun.connect({
hostname: "example.com",
port: 443,
tls: true,
socket: {
/* handlers */
},
});Hot Reload
server.reload({
socket: {
data(socket, data) {
/* new handler */
},
},
});Buffering Best Practice
// ❌ Slow - multiple syscalls
socket.write("h");
socket.write("e");
socket.write("l");
// ✅ Fast - single syscall
socket.write("hello");Socket Methods
socket.write(data); // Send data
socket.end(); // Close gracefully
socket.terminate(); // Close immediately
socket.flush(); // Flush buffer
socket.timeout(seconds); // Set timeout
socket.ref() / socket.unref();---
UDP
Create & Send
// Create socket
const socket = await Bun.udpSocket({ port: 41234 });
// Send datagram (no DNS - use IP)
socket.send("Hello", 41234, "127.0.0.1");Receive
const server = await Bun.udpSocket({
socket: {
data(socket, buf, port, addr) {
console.log(`From ${addr}:${port}: ${buf.toString()}`);
},
},
});Error and truncation handling (v1.3.12)
const socket = await Bun.udpSocket({
socket: {
error(err) {
console.log(err.code);
},
data(socket, data, port, address, flags) {
if (flags.truncated) {
console.log("Datagram truncated");
}
},
},
});- ICMP failures such as
ECONNREFUSEDnow surface through theerrorhandler instead of silently closing the socket. - The extra
flagsargument lets you distinguish truncated datagrams from complete payloads.
Connected Socket
const client = await Bun.udpSocket({
connect: {
hostname: "127.0.0.1",
port: 41234,
},
});
client.send("Hello"); // No destination neededBatch Sending
// Unconnected: [data, port, addr, ...]
socket.sendMany(["Hello", 41234, "127.0.0.1", "World", 53, "1.1.1.1"]);
// Connected: [data, ...]
connectedSocket.sendMany(["foo", "bar", "baz"]);Multicast
socket.addMembership("224.0.0.1");
socket.dropMembership("224.0.0.1");
socket.setMulticastTTL(2);
socket.setMulticastLoopback(true);---
Key Points
| Aspect | TCP | UDP |
|---|---|---|
| Connection | Connection-oriented | Connectionless |
| Reliability | Guaranteed delivery | Best effort |
| Use case | HTTP, DB, files | Real-time, gaming |
| Buffering | Manual batching | Auto per datagram |
| DNS | Resolved | IP addresses only |
Common rules:
- Use
drainhandler for backpressure - Batch writes for performance
socket.datafor per-connection state (TCP)unref()to not block process exit
Unix domain socket lifecycle (v1.3.12)
- Binding to an existing unix socket path now throws
EADDRINUSEinstead of silently replacing the socket file. - Closing a unix listener automatically cleans up the socket file for
Bun.listen,Bun.serve, andnet.Servercompatibility paths.
Bun Node.js Compatibility
Bun aims for 100% Node.js API compatibility. Most frameworks (Next.js, Express) and npm packages work out of the box.
Policy: If a package works in Node.js but not in Bun, it's a bug — please report it.
Fully Implemented Modules
| Module | Status |
|---|---|
node:assert | ✅ Full |
node:buffer | ✅ Full |
node:console | ✅ Full |
node:dgram | ✅ Full (>90% tests) |
node:diagnostics_channel | ✅ Full |
node:dns | ✅ Full (>90% tests) |
node:events | ✅ Full (100% tests) |
node:fs | ✅ Full (92% tests) |
node:http | ✅ Full |
node:net | ✅ Full |
node:os | ✅ Full (100% tests) |
node:path | ✅ Full (100% tests) |
node:punycode | ✅ Full (deprecated) |
node:querystring | ✅ Full (100% tests) |
node:readline | ✅ Full |
node:stream | ✅ Full |
node:string_decoder | ✅ Full (100% tests) |
node:timers | ✅ Full |
node:tty | ✅ Full |
node:url | ✅ Full |
node:zlib | ✅ Full (98% tests) |
Mostly Implemented Modules
| Module | Missing/Notes |
|---|---|
node:async_hooks | AsyncLocalStorage, AsyncResource work. V8 promise hooks not called. |
node:child_process | Missing proc.gid, proc.uid. IPC can't send socket handles. |
node:cluster | Works, but socket passing only on Linux (via SO_REUSEPORT). |
node:crypto | Missing secureHeapUsed, setEngine, setFips. |
node:http2 | 95%+ gRPC tests pass. Missing allowHTTP1, pushStream. |
node:https | Implemented, but Agent not always used. |
node:module | Missing syncBuiltinESMExports, module.register. |
node:perf_hooks | APIs implemented, tests not passing yet. |
node:tls | Missing tls.createSecurePair. |
node:util | Missing getCallSite, transferableAbortSignal. |
node:v8 | writeHeapSnapshot, getHeapSnapshot work. Use bun:jsc for profiling. |
node:vm | Core functionality works. Missing measureMemory, some cachedData. |
node:worker_threads | Missing some Worker options, markAsUntransferable. |
Partially/Not Implemented
| Module | Status |
|---|---|
node:domain | Missing Domain, active |
node:inspector | Profiler API works |
node:wasi | Partial |
node:test | Partial — use bun:test |
node:repl | ❌ Not implemented |
node:sqlite | ❌ Use bun:sqlite |
node:trace_events | ❌ Not implemented |
Globals
All standard Node.js globals are fully implemented:
AbortController,AbortSignalBuffer,Blobconsole,processfetch,Request,Response,Headers,FormDatasetTimeout,setInterval,setImmediate,clearTimeout,clearInterval,clearImmediateURL,URLSearchParamsTextEncoder,TextDecodercrypto,SubtleCrypto,CryptoKeyReadableStream,WritableStream,TransformStreamMessageChannel,MessagePort,BroadcastChannelstructuredClone,queueMicrotaskrequire,module,exports,__dirname,__filenameWebAssemblyEvent,EventTarget,CustomEventperformance,PerformanceObserver
process Differences
process.binding— partially implementedprocess.title— no-op on macOS/Linuxprocess.loadEnvFile,process.getBuiltinModule— not implementedgetActiveResourcesInfo— stub
Bun-Native Alternatives
| Node.js | Bun Alternative |
|---|---|
node:test | bun:test |
node:sqlite | bun:sqlite |
node:v8 profiling | bun:jsc |
child_process | Bun.spawn, Bun.$ |
node:crypto | Bun.CryptoHasher, Bun.password |
fs.readFile | Bun.file().text() |
fs.writeFile | Bun.write() |
Common Compatibility Patterns
ESM/CJS Interop
// Both work
import fs from "node:fs";
const fs = require("node:fs");
// Bun handles the conversion automatically
import pkg from "./cjs-package"; // Works even if CJSrequire.cache
// Supported for both ESM & CJS
delete require.cache[require.resolve("./module")];\_\_dirname in ESM
// Node.js ESM doesn't have __dirname
// Bun provides it, or use:
import.meta.dir; // Directory
import.meta.file; // Filename
import.meta.path; // Full pathChecking Runtime
if (typeof Bun !== "undefined") {
// Running in Bun
}
if (process.versions.bun) {
// Also works
}````markdown
Package Manager
Bun's npm-compatible package manager (25x faster than npm).
Core Commands
Install All
bun install # Install all dependencies
bun install --production # Skip devDependencies
bun install --frozen-lockfile # CI mode (fail if lockfile mismatch)
bun ci # Same as --frozen-lockfile````
Add Package
bun add react # Regular dependency
bun add -d typescript # Dev dependency
bun add --optional lodash # Optional dependency
bun add --peer @types/react # Peer dependency
bun add react@^18.0.0 # Specific version range
bun add react --exact # Pin exact version
bun add -g cowsay # Global installGit/URL Dependencies
bun add github:user/repo
bun add git+https://github.com/user/repo.git
bun add git+ssh://[email protected]:user/repo.git#tag
bun add https://example.com/package.tgzRemove Package
bun remove lodash
bun remove -g cowsay # Global uninstallUpdate Packages
bun update # Update all (within semver range)
bun update react # Update specific package
bun update --latest # Update to latest (ignore semver)
bun update -i # Interactive mode
bun update -i -r # Interactive across all workspacesCheck Outdated
bun outdated # List outdated packages
bun outdated 'eslint*' # Filter by pattern
bun outdated '!@types/*' # Exclude pattern
bun outdated --filter=pkg-a # Specific workspace---
Workspaces (Monorepo)
Setup
// package.json (root)
{
"workspaces": ["packages/*"],
"devDependencies": {
"shared": "workspace:*"
}
}Workspace Protocol
{
"dependencies": {
"pkg-a": "workspace:*", // Any version
"pkg-b": "workspace:^", // ^version
"pkg-c": "workspace:~" // ~version
}
}Install Specific Workspaces
bun install --filter 'pkg-*' # Match pattern
bun install --filter '!pkg-c' # Exclude
bun install --filter './packages/a' # By pathCatalogs (Shared Versions)
{
"workspaces": {
"packages": ["packages/*"],
"catalog": {
"react": "^18.0.0",
"typescript": "^5.0.0"
}
}
}Usage in workspace:
{
"dependencies": {
"react": "catalog:"
}
}---
Publishing
Publish Package
bun publish # Publish to npm
bun publish --tag beta # With tag
bun publish --access public # Public scoped package
bun publish --dry-run # Preview without publishing
bun publish --otp 123456 # With 2FA codePack Tarball
bun pm pack # Create .tgz
bun pm pack --dry-run # Show what would be included
bun pm pack --destination ./distpublishConfig (package.json)
{
"publishConfig": {
"access": "public",
"tag": "next",
"registry": "https://registry.npmjs.org"
}
}---
Patching Dependencies
# 1. Prepare for patching
bun patch lodash
# 2. Edit node_modules/lodash/...
# 3. Commit patch
bun patch --commit lodash
# Creates patches/lodash@4.17.21.patchPatches stored in patches/ directory, tracked in package.json:
{
"patchedDependencies": {
"[email protected]": "patches/lodash@4.17.21.patch"
}
}---
Overrides & Resolutions
Force specific metadependency versions:
{
"overrides": {
"bar": "~4.4.0"
},
"resolutions": {
"lodash": "4.17.21"
}
}---
Link (Local Development)
# In package directory
cd ~/my-lib && bun link
# In consuming project
bun link my-lib
# Unlink
bun unlink my-libCreates link: specifier:
{
"dependencies": {
"my-lib": "link:my-lib"
}
}---
Utility Commands
bun pm ls # List installed packages
bun pm ls --all # Include transitive deps
bun pm bin # Print local bin path
bun pm bin -g # Print global bin path
bun pm cache # Print cache path
bun pm cache rm # Clear cache
bun pm hash # Hash current lockfile---
CI/CD Configuration
GitHub Actions
- uses: oven-sh/setup-bun@v2
- run: bun ci # Use frozen lockfileTrusted Dependencies
{
"trustedDependencies": ["esbuild", "sharp"]
}bun add sharp --trust # Add to trustedDependenciesSupply Chain Protection
bun add pkg --minimum-release-age 259200 # 3 days# bunfig.toml
[install]
minimumReleaseAge = 259200
minimumReleaseAgeExcludes = ["typescript"]---
Installation Strategies
bun install --linker hoisted # npm-style (default for single)
bun install --linker isolated # pnpm-style (default for workspaces)| Strategy | Description |
|---|---|
hoisted | Traditional flat node_modules |
isolated | Strict deps, prevents phantom imports |
---
Configuration
bunfig.toml
[install]
optional = true
dev = true
peer = true
production = false
frozenLockfile = false
linker = "hoisted"
registry = "https://registry.npmjs.org"
[install.scopes]
"@myorg" = { token = "$NPM_TOKEN", url = "https://npm.myorg.com/" }Environment Variables
| Variable | Description |
|---|---|
BUN_CONFIG_REGISTRY | Default registry URL |
NPM_CONFIG_TOKEN | Auth token for publishing |
BUN_CONFIG_YARN_LOCKFILE | Generate yarn.lock |
BUN_CONFIG_SKIP_SAVE_LOCKFILE | Don't save lockfile |
---
Key Flags Reference
| Flag | Description |
|---|---|
--production / -p | Skip devDependencies |
--frozen-lockfile | Fail if lockfile needs update |
--dry-run | Preview without changes |
--force / -f | Re-fetch all from registry |
--exact / -E | Pin exact version |
--global / -g | Global install |
--dev / -d / -D | Add to devDependencies |
--omit dev | Exclude dev deps |
--filter <pattern> | Target specific workspaces |
--verbose | Debug logging |
--silent | No output |
Plugins
Universal plugin API for Bun runtime and bundler.
Plugin Structure
import type { BunPlugin } from "bun";
const myPlugin: BunPlugin = {
name: "my-plugin",
setup(build) {
// Register hooks here
},
};Usage
With Bundler
await Bun.build({
entrypoints: ["./app.ts"],
outdir: "./dist",
plugins: [myPlugin],
});With Runtime (preload)
// plugins.ts
import { plugin } from "bun";
plugin({
name: "yaml-loader",
setup(build) {
// ...
},
});# bunfig.toml
preload = ["./plugins.ts"]Lifecycle Hooks
onStart
Run when bundle starts:
build.onStart(() => {
console.log("Bundle started!");
});
// Async supported
build.onStart(async () => {
await setup();
});onResolve
Intercept module resolution:
build.onResolve({ filter: /^images\// }, (args) => {
return {
path: args.path.replace("images/", "./public/images/"),
};
});onLoad
Transform module contents:
build.onLoad({ filter: /\.yaml$/ }, async (args) => {
const text = await Bun.file(args.path).text();
const data = parseYAML(text);
return {
contents: `export default ${JSON.stringify(data)}`,
loader: "js",
};
});Available Loaders
js, jsx, ts, tsx, json, jsonc, toml, yaml, text, css, html, file, napi, wasm
Examples
Environment Variables Plugin
plugin({
name: "env-plugin",
setup(build) {
build.onLoad({ filter: /^env$/ }, () => ({
contents: `export default ${JSON.stringify(process.env)}`,
loader: "js",
}));
},
});
// Usage: import env from "env";YAML Loader
import YAML from "yaml";
plugin({
name: "yaml-loader",
setup(build) {
build.onLoad({ filter: /\.ya?ml$/ }, async ({ path }) => {
const text = await Bun.file(path).text();
return {
contents: `export default ${JSON.stringify(YAML.parse(text))}`,
loader: "js",
};
});
},
});Virtual Module
plugin({
name: "virtual-module",
setup(build) {
build.onResolve({ filter: /^virtual:config$/ }, () => ({
path: "virtual:config",
namespace: "virtual",
}));
build.onLoad({ filter: /.*/, namespace: "virtual" }, () => ({
contents: `export const version = "1.0.0"`,
loader: "js",
}));
},
});Namespaces
| Namespace | Description |
|---|---|
file | Default, local files |
bun | Bun modules (bun:sqlite) |
node | Node.js modules (node:fs) |
| custom | Your own namespace |
Deferred Loading
Wait for all other modules to load first:
build.onLoad({ filter: /stats\.json/ }, async ({ defer }) => {
await defer(); // Wait for all modules
return {
contents: JSON.stringify(collectedStats),
loader: "json",
};
});Native Plugins (Rust/C)
For maximum performance, write plugins as NAPI modules:
import nativeAddon from "./my-addon.node";
plugin({
name: "native-plugin",
setup(build) {
build.onBeforeParse(
{ filter: "**/*.tsx" },
{ napiModule: nativeAddon, symbol: "transform" }
);
},
});Native plugins run on multiple threads — significantly faster than JS plugins.
````markdown
Project Scaffolding
Quick project setup with bun init and bun create.
bun init
Create empty project:
bun init # Interactive
bun init -y # Accept defaults````
Creates:
package.jsontsconfig.jsonindex.ts(entry point).gitignoreREADME.md
Common options:
bun init my-app # Create in ./my-app
bun init --open # Open in editor
bun init --no-git # Skip git init---
bun create
Create from template:
# From npm package
bun create <package> [destination]
# From GitHub
bun create <user>/<repo>
bun create github.com/<user>/<repo>
# Framework starters
bun create vite my-app
bun create next-app my-app
bun create elysia my-apiPopular Templates
# Frontend
bun create vite my-app # Vite
bun create next-app my-app # Next.js
bun create react-app my-app # React
bun create svelte my-app # Svelte
# Backend
bun create elysia my-api # Elysia
bun create hono my-api # HonoGitHub Templates
bun create vercel/next.js my-next
bun create sveltejs/template my-svelteCustom npm Template
Package must export:
// package.json
{
"name": "create-my-template",
"module": "index.ts"
}
// index.ts
export default {
name: "my-template",
// files to copy from package
};---
Post-Create
cd my-app
bun install # Install dependencies
bun run dev # Start dev server---
React Component (File-Based)
bunx --bun react my-componentCreates MyComponent.tsx with boilerplate.
---
Key Points
bun init— blank project, minimal setupbun create— from templates (npm, GitHub)- Auto-detects and configures TypeScript
- All templates run
bun installautomatically - Use
-yto skip prompts
Bun Redis Client
Native Redis client with Promise-based API. Supports Redis 7.2+.
Connection
import { redis, RedisClient } from "bun";
// Global singleton (reads REDIS_URL or VALKEY_URL env var)
await redis.set("key", "value");
// Custom client
const client = new RedisClient("redis://localhost:6379");
// With auth
const client = new RedisClient("redis://user:pass@localhost:6379/0");
// TLS
const client = new RedisClient("rediss://localhost:6379");
// or
const client = new RedisClient("redis+tls://localhost:6379");
// Unix socket
const client = new RedisClient("redis+unix:///path/to/socket");Environment Variables
Checked in order:
1. REDIS_URL 2. VALKEY_URL 3. Default: redis://localhost:6379
Connection Lifecycle
const client = new RedisClient();
// Auto-connect on first command
await client.set("key", "value");
// Or explicit connect
await client.connect();
// Check status
console.log(client.connected); // boolean
console.log(client.bufferedAmount); // bytes buffered
// Close when done
client.close();Connection Events
client.onconnect = () => {
console.log("Connected");
};
client.onclose = (error) => {
console.error("Disconnected:", error);
};Connection Options
const client = new RedisClient("redis://localhost:6379", {
connectionTimeout: 5000, // ms (default: 10000)
idleTimeout: 30000, // ms (default: 0 = no timeout)
autoReconnect: true, // default: true
maxRetries: 10, // default: 10
enableOfflineQueue: true, // queue commands when disconnected
enableAutoPipelining: true, // batch commands automatically
tls: true, // or { ca, cert, key, rejectUnauthorized }
});String Operations
// Set/Get
await redis.set("key", "value");
const value = await redis.get("key");
// Get as Uint8Array
const buffer = await redis.getBuffer("key");
// Delete
await redis.del("key");
// Check existence
const exists = await redis.exists("key"); // boolean
// Expiration
await redis.expire("key", 3600); // seconds
const ttl = await redis.ttl("key");Numeric Operations
await redis.set("counter", "0");
await redis.incr("counter"); // +1
await redis.decr("counter"); // -1Hash Operations
// Set multiple fields
await redis.hmset("user:123", [
"name", "Alice",
"email", "[email protected]",
]);
// Get multiple fields
const [name, email] = await redis.hmget("user:123", ["name", "email"]);
// Get single field
const name = await redis.hget("user:123", "name");
// Increment numeric field
await redis.hincrby("user:123", "visits", 1);
await redis.hincrbyfloat("user:123", "score", 1.5);Set Operations
// Add member
await redis.sadd("tags", "javascript");
// Remove member
await redis.srem("tags", "javascript");
// Check membership
const isMember = await redis.sismember("tags", "js"); // boolean
// Get all members
const all = await redis.smembers("tags");
// Random member
const random = await redis.srandmember("tags");
// Pop random
const popped = await redis.spop("tags");Raw Commands
// Any Redis command
const info = await redis.send("INFO", []);
await redis.send("LPUSH", ["mylist", "v1", "v2"]);
const list = await redis.send("LRANGE", ["mylist", "0", "-1"]);Pub/Sub
Note: Subscription mode takes over the connection. Use .duplicate() for commands.
const redis = new RedisClient("redis://localhost:6379");
await redis.connect();
// Duplicate for commands while subscribed
const subscriber = await redis.duplicate();
// Subscribe
await subscriber.subscribe("channel", (message, channel) => {
console.log(`${channel}: ${message}`);
});
// Publish (from non-subscribed client)
await redis.publish("channel", "Hello!");
// Unsubscribe
await subscriber.unsubscribe(); // all channels
await subscriber.unsubscribe("channel"); // specific channelPipelining
Commands are automatically pipelined:
// These run concurrently
const [a, b] = await Promise.all([
redis.get("key1"),
redis.get("key2"),
]);Disable if needed:
const client = new RedisClient(url, {
enableAutoPipelining: false,
});Type Conversion
| Redis Type | JavaScript Type |
|---|---|
| Integer | number |
| Bulk string | string |
| Null | null |
| Array | Array |
| Boolean (RESP3) | boolean |
| Map (RESP3) | Object |
| Set (RESP3) | Array |
Special cases:
EXISTS→booleanSISMEMBER→boolean
Error Handling
try {
await redis.get("key");
} catch (error) {
switch (error.code) {
case "ERR_REDIS_CONNECTION_CLOSED":
// Connection lost
break;
case "ERR_REDIS_AUTHENTICATION_FAILED":
// Auth failed
break;
case "ERR_REDIS_INVALID_RESPONSE":
// Invalid response
break;
}
}Reconnection
Automatic exponential backoff:
- Starts at 50ms, doubles each attempt
- Capped at 2000ms
- Up to
maxRetriesattempts (default: 10) - Commands queued if
enableOfflineQueue: true
Practical Patterns
Caching
async function getUserCached(userId: string) {
const key = `user:${userId}`;
const cached = await redis.get(key);
if (cached) return JSON.parse(cached);
const user = await db.getUser(userId);
await redis.set(key, JSON.stringify(user));
await redis.expire(key, 3600);
return user;
}Rate Limiting
async function rateLimit(ip: string, limit = 100, windowSecs = 3600) {
const key = `ratelimit:${ip}`;
const count = await redis.incr(key);
if (count === 1) {
await redis.expire(key, windowSecs);
}
return {
limited: count > limit,
remaining: Math.max(0, limit - count),
};
}Session Storage
async function createSession(userId: number, data: object) {
const sessionId = crypto.randomUUID();
const key = `session:${sessionId}`;
await redis.hmset(key, [
"userId", String(userId),
"created", String(Date.now()),
"data", JSON.stringify(data),
]);
await redis.expire(key, 86400); // 24h
return sessionId;
}
async function getSession(sessionId: string) {
const key = `session:${sessionId}`;
if (!await redis.exists(key)) return null;
const [userId, created, data] = await redis.hmget(key, [
"userId", "created", "data",
]);
return {
userId: Number(userId),
created: Number(created),
data: JSON.parse(data!),
};
}Limitations
Current:
- Transactions (MULTI/EXEC) via raw commands only
Unsupported:
- Redis Sentinel
- Redis Cluster
Bun S3 API
Native bindings for S3-compatible object storage (AWS S3, Cloudflare R2, DigitalOcean Spaces, MinIO, Google Cloud Storage, Supabase).
Core Concepts
Bun.s3— global singleton using environment variablesBun.S3Client— explicit credentials clientS3File— lazy reference extendingBlob- Zero network requests until you call a method that needs one
Environment Variables
# Primary (Bun-specific)
S3_ACCESS_KEY_ID=...
S3_SECRET_ACCESS_KEY=...
S3_REGION=us-east-1
S3_BUCKET=my-bucket
S3_ENDPOINT=https://s3.us-east-1.amazonaws.com
# Fallback (AWS-style)
AWS_ACCESS_KEY_ID=...
AWS_SECRET_ACCESS_KEY=...
AWS_REGION=...Basic Usage
import { s3, S3Client } from "bun";
// Using global singleton (reads env vars)
const file = s3.file("path/to/file.json");
// Using explicit client
const client = new S3Client({
accessKeyId: "...",
secretAccessKey: "...",
bucket: "my-bucket",
endpoint: "https://s3.us-east-1.amazonaws.com",
});
const file = client.file("path/to/file.json");Reading Files
const file = s3.file("data.json");
// All methods return Promise
const text = await file.text();
const json = await file.json();
const buffer = await file.arrayBuffer();
const bytes = await file.bytes();
// Partial read (HTTP Range header)
const partial = await file.slice(0, 1024).text();
// Stream
for await (const chunk of file.stream()) {
console.log(chunk);
}Writing Files
const file = s3.file("output.json");
// Simple write
await file.write("Hello World!");
await file.write(Buffer.from("binary data"));
await file.write(new Response("from response"));
// With options
await file.write(JSON.stringify(data), {
type: "application/json",
});
await file.write(compressedData, {
type: "application/json",
contentEncoding: "gzip",
});
await file.write(pdfData, {
type: "application/pdf",
contentDisposition: 'attachment; filename="report.pdf"',
});
// Using Bun.write
await Bun.write(s3.file("output.txt"), "Hello World!");Streaming Large Files
const file = s3.file("large-file.bin");
const writer = file.writer({
type: "application/octet-stream",
retry: 3, // Retry on network errors
queueSize: 10, // Concurrent uploads
partSize: 5 * 1024 * 1024, // 5MB chunks
});
for (const chunk of chunks) {
writer.write(chunk);
await writer.flush();
}
await writer.end();Presigned URLs
// Download URL (default: GET, 24 hours)
const downloadUrl = s3.presign("file.txt");
// Upload URL
const uploadUrl = s3.presign("file.json", {
method: "PUT",
expiresIn: 3600, // 1 hour
type: "application/json",
acl: "public-read",
});
// Force download
const forceDownload = s3.presign("report.pdf", {
contentDisposition: 'attachment; filename="quarterly-report.pdf"',
});ACL Options
| ACL | Description |
|---|---|
"private" | Bucket owner only |
"public-read" | Public readable |
"public-read-write" | Public read/write |
"authenticated-read" | Authenticated users |
"bucket-owner-read" | Bucket owner readable |
"bucket-owner-full-control" | Bucket owner full control |
File Operations
// Check existence
const exists = await file.exists();
// Get metadata
const stat = await file.stat();
// { etag, lastModified, size, type }
// Get size only
const bytes = await S3Client.size("file.txt", credentials);
// Delete
await file.delete();
// or
await file.unlink();Listing Objects
// List up to 1000 objects
const result = await S3Client.list(null, credentials);
// With prefix/pagination
const uploads = await S3Client.list({
prefix: "uploads/",
maxKeys: 500,
fetchOwner: true,
}, credentials);
// Continue if truncated
if (uploads.isTruncated) {
const more = await S3Client.list({
prefix: "uploads/",
startAfter: uploads.contents.at(-1).key,
}, credentials);
}S3-Compatible Services
AWS S3
const s3 = new S3Client({
accessKeyId: "...",
secretAccessKey: "...",
bucket: "my-bucket",
region: "us-east-1",
});Cloudflare R2
const r2 = new S3Client({
accessKeyId: "...",
secretAccessKey: "...",
bucket: "my-bucket",
endpoint: "https://<account-id>.r2.cloudflarestorage.com",
});Google Cloud Storage
const gcs = new S3Client({
accessKeyId: "...",
secretAccessKey: "...",
bucket: "my-bucket",
endpoint: "https://storage.googleapis.com",
});DigitalOcean Spaces
const spaces = new S3Client({
accessKeyId: "...",
secretAccessKey: "...",
bucket: "my-bucket",
endpoint: "https://nyc3.digitaloceanspaces.com",
});MinIO
const minio = new S3Client({
accessKeyId: "...",
secretAccessKey: "...",
bucket: "my-bucket",
endpoint: "http://localhost:9000",
});Supabase
const supabase = new S3Client({
accessKeyId: "...",
secretAccessKey: "...",
bucket: "my-bucket",
region: "us-west-1",
endpoint: "https://<account-id>.supabase.co/storage/v1/s3/storage",
});Virtual Hosted-Style
const s3 = new S3Client({
accessKeyId: "...",
secretAccessKey: "...",
bucket: "my-bucket",
virtualHostedStyle: true,
// Endpoint auto-inferred from region+bucket
});s3:// Protocol
// Works with fetch
const response = await fetch("s3://my-bucket/file.txt");
// Works with Bun.file
const file = Bun.file("s3://my-bucket/file.txt");
// With credentials
const response = await fetch("s3://my-bucket/file.txt", {
s3: {
accessKeyId: "...",
secretAccessKey: "...",
},
headers: {
range: "bytes=0-1023",
},
});Quick Redirect to Presigned URL
// Returns 302 redirect to presigned URL
// Saves bandwidth by not downloading through your server
const response = new Response(s3.file("large-file.zip"));Error Handling
try {
await file.text();
} catch (error) {
// Bun errors
if (error.code === "ERR_S3_MISSING_CREDENTIALS") { ... }
if (error.code === "ERR_S3_INVALID_PATH") { ... }
// S3 service errors
if (error.name === "S3Error") { ... }
}Error Codes
| Code | Description |
|---|---|
ERR_S3_MISSING_CREDENTIALS | Credentials not provided |
ERR_S3_INVALID_METHOD | Invalid HTTP method |
ERR_S3_INVALID_PATH | Invalid file path |
ERR_S3_INVALID_ENDPOINT | Invalid endpoint URL |
ERR_S3_INVALID_SIGNATURE | Signature mismatch |
ERR_S3_INVALID_SESSION_TOKEN | Invalid session token |
Practical Patterns
File Upload API
Bun.serve({
async fetch(req) {
if (req.method === "POST") {
const file = s3.file(`uploads/${Date.now()}`);
await file.write(req);
return new Response(JSON.stringify({
url: file.presign({ expiresIn: 3600 }),
}));
}
return new Response("Upload endpoint", { status: 405 });
},
});Direct User Upload
// Generate presigned PUT URL for client
const uploadUrl = s3.presign(`user-uploads/${userId}/${filename}`, {
method: "PUT",
expiresIn: 300,
type: contentType,
});
// Client uploads directly to S3
// Your server never handles the file dataDownload Proxy with Redirect
Bun.serve({
fetch(req) {
const url = new URL(req.url);
const key = url.pathname.slice(1);
// Redirect to presigned URL
return new Response(s3.file(key));
},
});Bun Shell
Cross-platform bash-like shell with JavaScript interop.
Basic Usage
import { $ } from "bun";
await $`echo "Hello World!"`;Features
- Cross-platform (Windows, Linux, macOS)
- Native glob support (
**,*,{expansion}) - Auto-escaping prevents shell injection
- JavaScript object interop (Response, Buffer, Bun.file)
- Concurrent execution
Output Handling
// Print to stdout (default)
await $`echo "Hello"`;
// Quiet mode (no output)
await $`echo "Hello"`.quiet();
// Get as text
const text = await $`echo "Hello"`.text();
// Get as JSON
const json = await $`echo '{"a":1}'`.json();
// Get as Blob
const blob = await $`echo "Hello"`.blob();
// Get stdout/stderr buffers
const { stdout, stderr } = await $`echo "Hello"`.quiet();
// Line-by-line
for await (const line of $`cat file.txt`.lines()) {
console.log(line);
}Error Handling
// Default: throws on non-zero exit
try {
await $`command-that-fails`.text();
} catch (err) {
console.log(err.exitCode);
console.log(err.stdout.toString());
console.log(err.stderr.toString());
}
// Disable throwing
const { exitCode, stdout, stderr } = await $`command`.nothrow().quiet();
// Global default
$.nothrow(); // Disable throws globally
$.throws(true); // Re-enableEnvironment Variables
// Inline
await $`FOO=bar bun -e 'console.log(process.env.FOO)'`;
// Interpolated
const value = "bar123";
await $`FOO=${value} bun -e 'console.log(process.env.FOO)'`;
// Set for single command
await $`echo $FOO`.env({ ...process.env, FOO: "bar" });
// Set globally
$.env({ FOO: "bar" });
await $`echo $FOO`; // barWorking Directory
// Single command
await $`pwd`.cwd("/tmp"); // /tmp
// Global default
$.cwd("/tmp");
await $`pwd`; // /tmpRedirection
Output Redirection
// To file
await $`echo "Hello" > file.txt`;
await $`echo "More" >> file.txt`; // Append
// To JavaScript objects
const buffer = Buffer.alloc(100);
await $`echo "Hello" > ${buffer}`;
// Stderr
await $`command 2> errors.txt`;
await $`command &> all.txt`; // Both stdout+stderrInput Redirection
// From file
await $`cat < input.txt`;
// From Response
const response = await fetch("https://example.com");
await $`cat < ${response} | wc -c`;
// From Buffer
const data = Buffer.from("hello");
await $`cat < ${data}`;
// From Bun.file
await $`cat < ${Bun.file("input.txt")}`;Stream Redirection
await $`command 2>&1`; // stderr to stdout
await $`command 1>&2`; // stdout to stderrPiping
const result = await $`echo "Hello World" | wc -w`.text();
// "2\n"
// With JavaScript objects
const response = new Response("hello world");
await $`cat < ${response} | wc -w`.text();
// "2\n"Command Substitution
await $`echo "Current commit: $(git rev-parse HEAD)"`;
// With shell variables
await $`
REV=$(git rev-parse HEAD)
docker build -t myapp:$REV .
`;String Interpolation
All interpolated values are automatically escaped:
const userInput = "file.txt; rm -rf /";
await $`ls ${userInput}`; // Safe: treated as literal stringRaw (Unescaped) Strings
await $`echo ${{ raw: "$(date)" }}`; // Executes date commandBuiltin Commands
Cross-platform without PATH:
| Command | Description |
|---|---|
cd | Change directory |
ls | List files (-l supported) |
rm | Remove files/dirs |
mkdir | Create directory |
mv | Move files |
cat | Print file contents |
echo | Print text |
pwd | Print working directory |
touch | Create/update file |
which | Locate command |
exit | Exit shell |
true | Exit 0 |
false | Exit 1 |
yes | Output "y" repeatedly |
seq | Print number sequence |
dirname | Directory part of path |
basename | Filename part of path |
Utilities
Brace Expansion
const expanded = await $.braces(`echo {1,2,3}`);
// ["echo 1", "echo 2", "echo 3"]Escape Strings
const escaped = $.escape('$(foo) `bar` "baz"');
// \$(foo) \`bar\` \"baz\"Shell Scripts
Run .sh files cross-platform:
# script.sh
echo "Hello from $(pwd)"bun ./script.sh # Works on Linux/macOS/WindowsPractical Patterns
Build Script
import { $ } from "bun";
await $`rm -rf dist`;
await $`mkdir -p dist`;
const version = await $`git describe --tags`.text();
await $`echo "Building ${version.trim()}"`;
await $`bun build src/index.ts --outdir dist`;Parallel Commands
await Promise.all([
$`bun run lint`,
$`bun run typecheck`,
$`bun run test`,
]);Git Operations
const branch = await $`git branch --show-current`.text();
const status = await $`git status --porcelain`.text();
if (status.trim()) {
await $`git add -A`;
await $`git commit -m "Auto commit"`;
}File Processing
for await (const line of $`cat data.csv | tail -n +2`.lines()) {
const [id, name] = line.split(",");
await $`curl -X POST http://api/items -d '{"id":"${id}","name":"${name}"}'`;
}Docker Workflow
const tag = `myapp:${Date.now()}`;
await $`docker build -t ${tag} .`;
await $`docker push ${tag}`;
await $`kubectl set image deployment/myapp myapp=${tag}`;Security
Safe by default: Interpolated values are escaped.
⚠️ Unsafe patterns:
// Spawning new shell loses protection
await $`bash -c "echo ${userInput}"`; // UNSAFE
// Argument injection still possible
await $`git ls-remote origin ${branch}`; // Git interprets flagsAlways sanitize user input before passing to commands.
Bun Spawn
Spawn child processes with Bun.spawn() (async) or Bun.spawnSync() (blocking).
Basic Usage
const proc = Bun.spawn(["bun", "--version"]);
await proc.exited; // Wait for process to finish
console.log(proc.exitCode); // 0Options
const proc = Bun.spawn(["command", "arg1"], {
cwd: "./path/to/dir",
env: { ...process.env, FOO: "bar" },
timeout: 5000, // Kill after 5s
killSignal: "SIGKILL", // Signal for timeout/abort
signal: abortController.signal,
onExit(proc, exitCode, signalCode, error) {
console.log("Exited:", exitCode);
},
});Input (stdin)
// From fetch Response
Bun.spawn(["cat"], {
stdin: await fetch("https://example.com"),
});
// From file
Bun.spawn(["cat"], {
stdin: Bun.file("input.txt"),
});
// Incremental write with pipe
const proc = Bun.spawn(["cat"], {
stdin: "pipe",
});
proc.stdin.write("hello ");
proc.stdin.write("world");
proc.stdin.flush();
proc.stdin.end();
// From ReadableStream
Bun.spawn(["cat"], {
stdin: new ReadableStream({
start(controller) {
controller.enqueue("Hello");
controller.close();
},
}),
});
// From Buffer/TypedArray
Bun.spawn(["cat"], {
stdin: Buffer.from("data"),
});stdin Options
| Value | Description |
|---|---|
null | No input (default) |
"pipe" | Returns FileSink for writing |
"inherit" | Use parent's stdin |
Bun.file() | Read from file |
Response | Use response body |
ReadableStream | Stream input |
Buffer/TypedArray | Binary data |
number | File descriptor |
Output (stdout/stderr)
const proc = Bun.spawn(["echo", "hello"]);
// Read as text
const text = await proc.stdout.text();
// Read as bytes
const bytes = await proc.stdout.bytes();
// Read as JSON
const json = await proc.stdout.json();
// Stream
for await (const chunk of proc.stdout) {
console.log(chunk);
}stdout/stderr Options
| Value | Description |
|---|---|
"pipe" | Default stdout; returns ReadableStream |
"inherit" | Default stderr; use parent's stream |
"ignore" | Discard output |
Bun.file() | Write to file |
number | File descriptor |
Bun.spawn(["command"], {
stdout: Bun.file("out.log"),
stderr: Bun.file("err.log"),
});Exit Handling
const proc = Bun.spawn(["command"]);
// Wait for exit
await proc.exited;
// Properties after exit
proc.exitCode; // number | null
proc.signalCode; // "SIGTERM" | null
proc.killed; // booleanKilling Processes
proc.kill(); // Default signal
proc.kill("SIGTERM"); // By name
proc.kill(15); // By numberProcess Lifecycle
// Detach from parent (don't block parent exit)
proc.unref();
// Re-attach
proc.ref();Resource Usage
await proc.exited;
const usage = proc.resourceUsage();
console.log(usage.maxRSS); // Max memory (bytes)
console.log(usage.cpuTime.user); // User CPU time (µs)
console.log(usage.cpuTime.system); // System CPU time (µs)AbortSignal
const controller = new AbortController();
const proc = Bun.spawn({
cmd: ["sleep", "100"],
signal: controller.signal,
});
// Later
controller.abort();Timeout
const proc = Bun.spawn({
cmd: ["sleep", "10"],
timeout: 5000, // Kill after 5 seconds
killSignal: "SIGKILL", // Signal to send
});Inter-Process Communication (IPC)
Parent Process
const child = Bun.spawn(["bun", "child.ts"], {
ipc(message, childProc) {
console.log("From child:", message);
childProc.send("Response from parent");
},
serialization: "json", // Required for Node.js compat
});
child.send("Hello child");
// Later
child.disconnect();Child Process
// child.ts
process.on("message", (message) => {
console.log("From parent:", message);
});
process.send("Hello parent");
process.send({ type: "data", value: 42 });Serialization Options
| Value | Description |
|---|---|
"advanced" | Default; JSC serialize (more types) |
"json" | JSON.stringify; required for Node.js |
Terminal (PTY) Support
POSIX only (Linux, macOS)
const proc = Bun.spawn(["bash"], {
terminal: {
cols: 80,
rows: 24,
data(terminal, data) {
process.stdout.write(data);
},
},
});
// Write to terminal
proc.terminal.write("echo hello\n");
// Resize
proc.terminal.resize(120, 40);
// Raw mode
proc.terminal.setRawMode(true);
await proc.exited;
proc.terminal.close();Reusable Terminal
await using terminal = new Bun.Terminal({
cols: 80,
rows: 24,
data(term, data) {
process.stdout.write(data);
},
});
const proc1 = Bun.spawn(["echo", "first"], { terminal });
await proc1.exited;
const proc2 = Bun.spawn(["echo", "second"], { terminal });
await proc2.exited;Synchronous API
const result = Bun.spawnSync(["echo", "hello"]);
result.success; // boolean
result.exitCode; // number
result.stdout; // Buffer
result.stderr; // Buffer
console.log(result.stdout.toString()); // "hello\n"maxBuffer (spawnSync only)
const result = Bun.spawnSync({
cmd: ["yes"],
maxBuffer: 100, // Kill after 100 bytes output
});Practical Patterns
Run Command and Get Output
async function run(cmd: string[]): Promise<string> {
const proc = Bun.spawn(cmd, {
stdout: "pipe",
stderr: "pipe",
});
const exitCode = await proc.exited;
if (exitCode !== 0) {
throw new Error(await proc.stderr.text());
}
return proc.stdout.text();
}
const version = await run(["git", "--version"]);Pipe Between Processes
const proc1 = Bun.spawn(["cat", "file.txt"]);
const proc2 = Bun.spawn(["grep", "pattern"], {
stdin: proc1.stdout,
});
const result = await proc2.stdout.text();Long-Running Process with Timeout
const proc = Bun.spawn({
cmd: ["long-running-task"],
timeout: 30000,
killSignal: "SIGTERM",
});
try {
await proc.exited;
} catch (err) {
if (proc.killed) {
console.log("Process timed out");
}
}Interactive Shell Session
const proc = Bun.spawn(["bash"], {
terminal: {
cols: 80,
rows: 24,
data(_, data) {
process.stdout.write(data);
},
},
});
// Forward user input
process.stdin.on("data", (chunk) => {
proc.terminal.write(chunk);
});
await proc.exited;Performance
- Uses
posix_spawn(3)under the hood spawnSyncis 60% faster than Node.jschild_process
SQLite
Native high-performance SQLite3 driver via bun:sqlite.
Basic Usage
import { Database } from "bun:sqlite";
const db = new Database("mydb.sqlite");
// const db = new Database(":memory:"); // In-memory
const query = db.query("SELECT * FROM users WHERE id = ?");
const user = query.get(1);Database Options
// Read-only
new Database("mydb.sqlite", { readonly: true });
// Create if not exists
new Database("mydb.sqlite", { create: true });
// Strict mode (throws on missing params)
new Database(":memory:", { strict: true });
// BigInt support for large integers
new Database(":memory:", { safeIntegers: true });Import via ES Module
import db from "./mydb.sqlite" with { type: "sqlite" };Queries
Prepare Statement
const query = db.query("SELECT * FROM users WHERE id = ?");Execute Methods
// All results as array of objects
query.all(1);
// [{ id: 1, name: "John" }]
// First result
query.get(1);
// { id: 1, name: "John" } or undefined
// Execute (for INSERT/UPDATE/DELETE)
query.run(1);
// { lastInsertRowid: 5, changes: 1 }
// Results as arrays
query.values(1);
// [[1, "John"]]
// Iterate (memory efficient)
for (const row of query.iterate()) {
console.log(row);
}Parameters
// Positional
db.query("SELECT ?1, ?2").all("a", "b");
// Named (with prefix)
db.query("SELECT $name, $age").all({ $name: "John", $age: 30 });
// Named (strict mode, no prefix)
db.query("SELECT $name").all({ name: "John" }); // requires strict: trueMap to Class
class User {
id: number;
name: string;
get displayName() {
return `User #${this.id}: ${this.name}`;
}
}
const users = db.query("SELECT * FROM users").as(User).all();
console.log(users[0].displayName);Transactions
const insert = db.prepare("INSERT INTO users (name) VALUES ($name)");
const insertMany = db.transaction((users) => {
for (const user of users) {
insert.run(user);
}
return users.length;
});
// Auto-commit on success, rollback on error
const count = insertMany([
{ $name: "Alice" },
{ $name: "Bob" },
]);
// Transaction types
insertMany.deferred(users); // BEGIN DEFERRED
insertMany.immediate(users); // BEGIN IMMEDIATE
insertMany.exclusive(users); // BEGIN EXCLUSIVEWAL Mode
Enable for better concurrent performance:
db.run("PRAGMA journal_mode = WAL;");Quick Operations
// Run SQL directly (for DDL, bulk writes)
db.run("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)");
db.run("INSERT INTO users (name) VALUES (?)", ["John"]);Closing
db.close(); // Allow pending queries to finish
db.close(true); // Throw if pending queries
// Using statement (auto-close)
{
using db = new Database("mydb.sqlite");
// ... use db
} // Auto-closed hereSerialize/Deserialize
// Backup database to bytes
const backup = db.serialize();
// Restore from bytes
const restored = Database.deserialize(backup);Data Types
| JavaScript | SQLite |
|---|---|
string | TEXT |
number | INTEGER/DECIMAL |
boolean | INTEGER (1/0) |
Uint8Array | BLOB |
bigint | INTEGER |
null | NULL |
Performance Tips
- Use prepared statements (
db.query()) — cached and reused - Enable WAL mode for concurrent access
- Use transactions for bulk writes
- Use
.values()for raw arrays (faster than objects) - Use
safeIntegers: trueonly if needed
Example: REST API
import { Database } from "bun:sqlite";
const db = new Database("app.sqlite");
db.run("PRAGMA journal_mode = WAL");
db.run(`
CREATE TABLE IF NOT EXISTS todos (
id INTEGER PRIMARY KEY,
title TEXT NOT NULL,
done INTEGER DEFAULT 0
)
`);
const getTodos = db.query("SELECT * FROM todos");
const getTodo = db.query("SELECT * FROM todos WHERE id = ?");
const addTodo = db.query("INSERT INTO todos (title) VALUES (?) RETURNING *");
const toggleTodo = db.query("UPDATE todos SET done = NOT done WHERE id = ?");
Bun.serve({
routes: {
"/todos": {
GET: () => Response.json(getTodos.all()),
POST: async (req) => {
const { title } = await req.json();
return Response.json(addTodo.get(title));
},
},
"/todos/:id/toggle": (req) => {
toggleTodo.run(req.params.id);
return Response.json(getTodo.get(req.params.id));
},
},
});Bun Transpiler
Programmatic access to Bun's internal transpiler for TypeScript/JSX transformation.
Basic Usage
const transpiler = new Bun.Transpiler({
loader: "tsx", // "js" | "jsx" | "ts" | "tsx"
});
const code = `
import React from 'react';
export function Home(props: {title: string}) {
return <p>{props.title}</p>;
}
`;
const result = transpiler.transformSync(code);
// Returns vanilla JavaScript stringMethods
transformSync()
Synchronous transpilation (same thread):
const transpiler = new Bun.Transpiler({ loader: "tsx" });
const js = transpiler.transformSync(code);
// Override loader for specific code
transpiler.transformSync("<div>hi</div>", "jsx");transform()
Async transpilation (worker threadpool):
const js = await transpiler.transform(code);
// Override loader
await transpiler.transform(code, "tsx");Note: For most cases, transformSync is faster due to threadpool overhead.
scan()
Analyze imports and exports:
const code = `
import React from 'react';
import type {ReactNode} from 'react';
const val = require('./cjs.js');
import('./loader');
export const name = "hello";
`;
const result = transpiler.scan(code);
// {
// exports: ["name"],
// imports: [
// { path: "react", kind: "import-statement" },
// { path: "./cjs.js", kind: "require-call" },
// { path: "./loader", kind: "dynamic-import" }
// ]
// }scanImports()
Faster import scanning (slightly less accurate):
const imports = transpiler.scanImports(code);
// [
// { path: "react", kind: "import-statement" },
// { path: "./cjs.js", kind: "require-call" },
// { path: "./loader", kind: "dynamic-import" }
// ]Import Kinds
| Kind | Example |
|---|---|
"import-statement" | import x from 'y' |
"require-call" | require('y') |
"require-resolve" | require.resolve('y') |
"dynamic-import" | import('y') |
"import-rule" | @import 'y.css' |
"url-token" | url('y.png') |
Options
const transpiler = new Bun.Transpiler({
// Default loader
loader: "tsx",
// Target platform
target: "bun", // "browser" | "bun" | "node"
// Define constants
define: {
"process.env.NODE_ENV": '"production"',
},
// Custom tsconfig
tsconfig: {
compilerOptions: {
jsx: "react-jsx",
jsxImportSource: "preact",
},
},
// Remove unused imports
trimUnusedImports: true,
// Inline constant values
inline: true, // default
// Whitespace minification
minifyWhitespace: false,
// Export manipulation
exports: {
eliminate: ["debugOnly"],
replace: { "oldName": "newName" },
},
});Custom JSX
Preact
const transpiler = new Bun.Transpiler({
loader: "tsx",
tsconfig: JSON.stringify({
compilerOptions: {
jsx: "react-jsx",
jsxImportSource: "preact",
},
}),
});Emotion
const transpiler = new Bun.Transpiler({
loader: "tsx",
tsconfig: JSON.stringify({
compilerOptions: {
jsx: "react-jsx",
jsxImportSource: "@emotion/react",
},
}),
});Macros
Replace imports with macro implementations:
const transpiler = new Bun.Transpiler({
loader: "tsx",
macro: {
"react-relay": {
graphql: "bun-macro-relay/bun-macro-relay.tsx",
},
},
});Practical Examples
Build-Time Transform
const transpiler = new Bun.Transpiler({
loader: "tsx",
target: "browser",
define: {
"process.env.NODE_ENV": '"production"',
"__DEV__": "false",
},
trimUnusedImports: true,
minifyWhitespace: true,
});
const source = await Bun.file("src/app.tsx").text();
const output = transpiler.transformSync(source);
await Bun.write("dist/app.js", output);Dependency Analysis
const transpiler = new Bun.Transpiler({ loader: "tsx" });
async function getDependencies(file: string) {
const code = await Bun.file(file).text();
const { imports } = transpiler.scan(code);
return imports
.filter(i => i.kind === "import-statement")
.map(i => i.path);
}
const deps = await getDependencies("src/index.ts");
console.log("Dependencies:", deps);Hot Module Replacement
const transpiler = new Bun.Transpiler({
loader: "tsx",
target: "browser",
});
Bun.serve({
async fetch(req) {
const url = new URL(req.url);
if (url.pathname.endsWith(".tsx")) {
const file = `.${url.pathname}`;
const source = await Bun.file(file).text();
const code = transpiler.transformSync(source);
return new Response(code, {
headers: { "Content-Type": "application/javascript" },
});
}
return new Response("Not found", { status: 404 });
},
});Performance Notes
transformSyncruns in main thread — best for small filestransformuses worker pool — better for many large filesscanImportsis faster thanscanfor just listing imports- Type-only imports are automatically ignored
````markdown
TypeScript & JSX
Bun runs TypeScript and JSX natively without configuration.
TypeScript
Zero Config
// index.ts — just run it
bun run index.ts````
Bun transpiles internally, no separate type checking. Use IDE or tsc --noEmit.
tsconfig.json
Bun reads tsconfig.json or jsconfig.json automatically.
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"types": ["bun-types"],
"strict": true,
"noEmit": true
}
}Type Checking
# Install types
bun add -d bun-types typescript
# Type check
bunx tsc --noEmit
# Watch mode
bunx tsc --noEmit --watchBun-Specific Types
/// <reference types="bun-types" />
import type { Server } from "bun";
const server: Server = Bun.serve({
fetch(req) {
return new Response("Hello");
},
});---
JSX
Default (React)
// React JSX (default)
const element = <div className="foo">Hello</div>;
// → React.createElement("div", { className: "foo" }, "Hello")JSX Pragma
// Override for specific file
/** @jsx h */
import { h } from "preact";
const element = <div>Preact</div>;Configuration
tsconfig.json:
{
"compilerOptions": {
"jsx": "react-jsx", // Modern React 17+
"jsxImportSource": "react" // Source for jsx-runtime
}
}JSX modes:
| Mode | Description |
|---|---|
react | Classic React.createElement |
react-jsx | Modern auto-import from jsx-runtime |
react-jsxdev | Development mode with extra checks |
preserve | Keep JSX, don't transform |
Fragment Pragma
/** @jsxFrag Fragment */
import { Fragment } from "react";
const element = <>Content</>;Preact Example
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "preact"
}
}// Uses preact/jsx-runtime automatically
const App = () => <div>Preact App</div>;Solid.js Example
{
"compilerOptions": {
"jsx": "preserve",
"jsxImportSource": "solid-js"
}
}---
File Extensions
| Extension | Behavior |
|---|---|
.ts | TypeScript |
.tsx | TypeScript + JSX |
.jsx | JavaScript + JSX |
.mts, .cts | ES/CommonJS TypeScript |
---
Key Points
- No compilation step — run
.ts/.tsxdirectly - No type checking at runtime — use
tsc --noEmit jsx: "react-jsx"for modern React (auto-imports)jsxImportSourceto switch frameworks (Preact, Solid)- File pragmas (
@jsx,@jsxFrag) override per-file
Bun Utilities
Hashing, glob patterns, HTML rewriting, and other utilities.
---
Password Hashing (Bun.password)
Secure password hashing with argon2 and bcrypt.
// Hash (async)
const hash = await Bun.password.hash(password);
// Verify
const match = await Bun.password.verify(password, hash);
// Sync versions
const hash = Bun.password.hashSync(password);
const match = Bun.password.verifySync(password, hash);Argon2 (default)
const hash = await Bun.password.hash(password, {
algorithm: "argon2id", // "argon2id" | "argon2i" | "argon2d"
memoryCost: 4, // kibibytes
timeCost: 3, // iterations
});Bcrypt
const hash = await Bun.password.hash(password, {
algorithm: "bcrypt",
cost: 10, // 4-31
});Note: Bun auto-hashes passwords >72 bytes with SHA-512 before bcrypt.
---
Non-Cryptographic Hashing (Bun.hash)
Fast hashing for non-security uses (default: Wyhash).
Bun.hash("data"); // bigint
Bun.hash("data", 1234); // with seed
Bun.hash(new Uint8Array([1, 2, 3])); // TypedArray
// Other algorithms
Bun.hash.crc32("data");
Bun.hash.adler32("data");
Bun.hash.cityHash32("data");
Bun.hash.cityHash64("data");
Bun.hash.xxHash32("data");
Bun.hash.xxHash64("data");
Bun.hash.xxHash3("data");
Bun.hash.murmur32v3("data");
Bun.hash.murmur64v2("data");
Bun.hash.rapidhash("data");---
Cryptographic Hashing (Bun.CryptoHasher)
Incremental cryptographic hashing.
const hasher = new Bun.CryptoHasher("sha256");
hasher.update("hello");
hasher.update(" world");
// Output formats
hasher.digest(); // Uint8Array
hasher.digest("hex"); // string
hasher.digest("base64"); // string
// Write to existing buffer
const buffer = new Uint8Array(32);
hasher.digest(buffer);Supported Algorithms
blake2b256, blake2b512, md4, md5, ripemd160, sha1, sha224, sha256, sha384, sha512, sha512-224, sha512-256, sha3-224, sha3-256, sha3-384, sha3-512, shake128, shake256
HMAC
const hasher = new Bun.CryptoHasher("sha256", "secret-key");
hasher.update("data");
console.log(hasher.digest("hex"));---
Glob (Bun.Glob)
Fast native glob pattern matching.
Matching Strings
const glob = new Glob("*.ts");
glob.match("index.ts"); // true
glob.match("index.js"); // false
glob.match("src/index.ts"); // falseScanning Files
const glob = new Glob("**/*.ts");
// Async
for await (const file of glob.scan(".")) {
console.log(file);
}
// Sync
for (const file of glob.scanSync(".")) {
console.log(file);
}Scan Options
glob.scan({
cwd: "./src",
dot: true, // Match dotfiles
absolute: true, // Return absolute paths
followSymlinks: true,
onlyFiles: true, // default
});Pattern Syntax
| Pattern | Matches |
|---|---|
? | Single character |
* | Zero+ chars (not /) |
** | Zero+ chars (including /) |
[ab] | a or b |
[a-z] | Range |
[^ab] | Not a or b |
{a,b} | a or b |
!pattern | Negation |
\* | Literal * |
new Glob("**/*.{ts,tsx}"); // TypeScript files
new Glob("src/**/[A-Z]*.ts"); // PascalCase files
new Glob("!**/*.test.ts"); // Exclude tests---
HTMLRewriter
Streaming HTML transformation with CSS selectors.
Basic Usage
const rewriter = new HTMLRewriter().on("img", {
element(el) {
el.setAttribute("loading", "lazy");
},
});
const result = rewriter.transform(html);Input Types
rewriter.transform(new Response(html));
rewriter.transform(html);
rewriter.transform(Bun.file("index.html"));
rewriter.transform(new Blob([html]));Element Handlers
rewriter.on("div.content", {
element(el) {
// Attributes
el.setAttribute("class", "new");
el.getAttribute("id");
el.hasAttribute("id");
el.removeAttribute("class");
// Content
el.setInnerContent("text");
el.setInnerContent("<p>HTML</p>", { html: true });
// Position
el.before("before");
el.after("after");
el.prepend("first child");
el.append("last child");
// Removal
el.remove();
el.removeAndKeepContent();
// Properties
el.tagName; // lowercase
el.selfClosing; // boolean
el.removed; // boolean
// Iterate attributes
for (const [name, value] of el.attributes) {
console.log(name, value);
}
// End tag
el.onEndTag((tag) => {
tag.before("before </div>");
tag.remove();
});
},
text(text) {
text.text; // content
text.lastInTextNode; // boolean
text.replace("new");
text.remove();
},
comments(comment) {
comment.text;
comment.text = "new";
comment.remove();
},
});CSS Selectors
rewriter.on("p", handler); // Tag
rewriter.on(".class", handler); // Class
rewriter.on("#id", handler); // ID
rewriter.on("[attr]", handler); // Has attribute
rewriter.on('[attr="value"]', handler); // Exact match
rewriter.on('[attr^="prefix"]', handler); // Starts with
rewriter.on('[attr$="suffix"]', handler); // Ends with
rewriter.on('[attr*="contains"]', handler);
rewriter.on("div > p", handler); // Direct child
rewriter.on("div p", handler); // Descendant
rewriter.on("p:first-child", handler); // Pseudo-class
rewriter.on("p:nth-child(2)", handler);
rewriter.on("*", handler); // UniversalDocument Handlers
rewriter.onDocument({
doctype(doctype) {
console.log(doctype.name);
},
text(text) {},
comments(comment) {},
end(end) {
end.append("<!-- Footer -->");
},
});---
Quick Reference
Semver
import { semver } from "bun";
semver.satisfies("1.2.3", "^1.0.0"); // true
semver.order("1.2.3", "1.2.4"); // -1Sleep
await Bun.sleep(1000); // milliseconds
await Bun.sleepSync(1000);Random
Bun.randomUUIDv7(); // UUID v7
crypto.randomUUID(); // UUID v4Inspect
Bun.inspect(object);
Bun.inspect(object, { depth: 2, colors: true });Path Utilities
Bun.main; // Entry point path
import.meta.dir; // Directory of current file
import.meta.file; // Filename of current file
import.meta.path; // Full path of current file---
Markdown Parser (Bun.markdown)
Built-in CommonMark-compliant Markdown parser (v1.3.8+).
Render Markdown for the terminal (v1.3.12)
const ansi = Bun.markdown.ansi("# Hello\n\n**bold**");
process.stdout.write(ansi);
const plain = Bun.markdown.ansi("# Hello", { colors: false });
const linked = Bun.markdown.ansi("[docs](https://bun.sh)", { hyperlinks: true });CLI shortcut:
bun ./README.mdUse Bun.markdown.ansi() when the output target is a terminal; keep html() or callback-based rendering for browser/UI pipelines.
Render to HTML
const html = Bun.markdown.html("# Hello **world**");
// "<h1>Hello <strong>world</strong></h1>\n"
// With options
Bun.markdown.html("## Hello", { headingIds: true });
// '<h2 id="hello">Hello</h2>\n'Custom Callbacks
const ansi = Bun.markdown.render("# Hello\n\n**bold**", {
heading: (children) => `\x1b[1;4m${children}\x1b[0m\n`,
paragraph: (children) => children + "\n",
strong: (children) => `\x1b[1m${children}\x1b[22m`,
});
// Return null to omit elements
Bun.markdown.render("# Title\n\n", {
image: () => null,
heading: (children) => children,
});React Elements
function Markdown({ text }: { text: string }) {
return Bun.markdown.react(text);
}
// With custom components
Bun.markdown.react("# Hello", {
h1: ({ children }) => <h1 className="title">{children}</h1>,
});
// React 18 compatibility
Bun.markdown.react(text, { reactVersion: 18 });GFM Extensions
Enabled by default: tables, strikethrough (~~deleted~~), task lists (- [x] done), autolinks.
Additional options: wikiLinks, latexMath, headingIds, autolinkHeadings.
WebSockets
Server-Side WebSockets
Basic Server
Bun.serve({
fetch(req, server) {
if (server.upgrade(req)) {
return; // Upgraded, no Response needed
}
return new Response("Not a WebSocket request", { status: 400 });
},
websocket: {
open(ws) {
console.log("Connected");
},
message(ws, message) {
ws.send(`Echo: ${message}`);
},
close(ws, code, reason) {
console.log("Disconnected");
},
drain(ws) {
// Ready to receive more data
},
},
});Upgrade with Headers/Data
Bun.serve({
fetch(req, server) {
const url = new URL(req.url);
const userId = url.searchParams.get("user");
server.upgrade(req, {
headers: {
"Set-Cookie": "session=abc123",
},
data: {
userId,
connectedAt: Date.now(),
},
});
},
websocket: {
message(ws, message) {
console.log(`User ${ws.data.userId}: ${message}`);
},
},
});TypeScript Data Typing
type WsData = {
userId: string;
room: string;
};
Bun.serve<WsData>({
websocket: {
message(ws, message) {
// ws.data is typed as WsData
console.log(ws.data.userId);
},
},
});Pub/Sub
Built-in topic-based broadcasting:
const server = Bun.serve({
websocket: {
open(ws) {
ws.subscribe("chat-room");
ws.publish("chat-room", "New user joined!");
},
message(ws, message) {
// Broadcast to all subscribers (except sender)
ws.publish("chat-room", message);
},
close(ws) {
ws.unsubscribe("chat-room");
},
},
});
// Server-level publish (to all subscribers)
server.publish("chat-room", "Server announcement!");Subscription Methods
ws.subscribe("topic"); // Join topic
ws.unsubscribe("topic"); // Leave topic
ws.publish("topic", message); // Send to others
ws.isSubscribed("topic"); // Check subscription
ws.subscriptions; // Get all topics
server.subscriberCount("topic"); // Count subscribersCompression
Bun.serve({
websocket: {
perMessageDeflate: true,
},
});
// Per-message compression
ws.send("Hello", true); // Compress this messageConfiguration
Bun.serve({
websocket: {
idleTimeout: 120, // Seconds (default: 120)
maxPayloadLength: 16 * 1024 * 1024, // Bytes (default: 16MB)
backpressureLimit: 1024 * 1024, // Bytes (default: 1MB)
closeOnBackpressureLimit: false,
sendPings: true,
publishToSelf: false,
},
});Backpressure Handling
const result = ws.send(message);
// -1: Enqueued but backpressure
// 0: Dropped (connection issue)
// 1+: Bytes sentClient-Side WebSocket
const socket = new WebSocket("ws://localhost:3000");
// Bun extension: custom headers
const socket = new WebSocket("ws://localhost:3000", {
headers: {
"Authorization": "Bearer token",
},
});
socket.addEventListener("open", () => {
socket.send("Hello");
});
socket.addEventListener("message", (event) => {
console.log(event.data);
});
socket.addEventListener("close", (event) => {
console.log(event.code, event.reason);
});Chat Example
const server = Bun.serve({
fetch(req, server) {
const url = new URL(req.url);
const username = url.searchParams.get("name") || "Anonymous";
if (server.upgrade(req, { data: { username } })) {
return;
}
return new Response("Expected WebSocket", { status: 400 });
},
websocket: {
open(ws) {
ws.subscribe("chat");
server.publish("chat", `${ws.data.username} joined`);
},
message(ws, message) {
server.publish("chat", `${ws.data.username}: ${message}`);
},
close(ws) {
server.publish("chat", `${ws.data.username} left`);
},
},
});Key Points
- Handlers declared once per server (not per socket) — more efficient
- Native pub/sub — no Redis needed for simple cases
ws.datafor per-connection stateserver.upgrade()returns boolean (true = success)- Return
undefinedafter successful upgrade, notResponse
Bun.WebView
Native headless browser automation built into the Bun runtime.
Backends
webkituses the system WebKit stack on macOS and needs no external browser install.chromeuses Chrome/Chromium through the DevTools Protocol and can auto-detect the browser or accept an explicit executable path.
Minimal usage
await using view = new Bun.WebView({ width: 1280, height: 720 });
await view.navigate("https://bun.sh");
await view.click("a[href='/docs']");
const title = await view.evaluate("document.title");
const screenshot = await view.screenshot({ format: "jpeg", quality: 90 });
await Bun.write("page.jpg", screenshot);What matters operationally
- Input is dispatched as native OS-level events, so click/type actions show up as trusted browser events.
- Selector-based actions wait for actionability (attached, visible, stable, and unobscured) before firing.
scrollTo(selector)walks ancestor scroll containers until the target becomes visible.- Chrome backend exposes raw
cdp(method, params)access when the high-level API is not enough. - One browser subprocess is shared per Bun process; additional
new Bun.WebView()calls open more tabs, not more browser instances.
Common API surface
navigate(url)evaluate(expr)screenshot({ format, quality, encoding })click(selector)orclick(x, y)type(text)press(key, { modifiers })scroll(dx, dy)/scrollTo(selector)goBack()/goForward()/reload()resize(width, height)
Practical rules
- Prefer the default WebKit backend on macOS when you want zero external dependencies.
- Use Chrome backend when you need CDP access, Chromium parity, or non-macOS support.
- Keep long-lived login state in
dataStoreinstead of rebuilding sessions on every run. - Capture page logs via the
consoleoption when the automation flow doubles as debugging. - Treat
Bun.WebViewas browser automation inside the app process, not as a general-purpose browser farm.
Bun Workers
Web Workers API for running JavaScript/TypeScript on separate threads.
Status: Experimental (termination still being improved).
Basic Usage
Main Thread
const worker = new Worker("./worker.ts");
worker.postMessage("hello");
worker.onmessage = (event) => {
console.log(event.data); // "world"
};Worker Thread
declare var self: Worker; // Prevents TS errors
self.onmessage = (event: MessageEvent) => {
console.log(event.data); // "hello"
postMessage("world");
};Creating Workers
// From file
const worker = new Worker("./worker.ts");
// From blob URL
const blob = new Blob([`postMessage("hi")`], {
type: "application/typescript",
});
const worker = new Worker(URL.createObjectURL(blob));
// From File object (with TypeScript support)
const file = new File([code], "worker.ts");
const worker = new Worker(URL.createObjectURL(file));Worker Options
const worker = new Worker("./worker.ts", {
ref: false, // Don't keep main process alive
smol: true, // Reduced memory mode
preload: ["./sentry.js"], // Load modules before worker starts
});Messages
Sending
// Main thread
worker.postMessage({ key: "value" });
// Worker thread
postMessage({ result: 42 });Receiving
// Main thread
worker.onmessage = (event) => {
console.log(event.data);
};
// Worker thread
self.onmessage = (event) => {
console.log(event.data);
};
// Or with addEventListener
worker.addEventListener("message", (event) => {
console.log(event.data);
});Performance Fast Paths
Bun optimizes postMessage for common types:
String fast path — no serialization:
postMessage("Hello"); // 2-241x fasterSimple object fast path — optimized for primitives:
postMessage({
message: "Hello",
count: 42,
enabled: true,
}); // Fast pathComplex objects use standard structured clone:
postMessage({
nested: { deep: true },
date: new Date(),
buffer: new ArrayBuffer(8),
}); // Standard pathEvents
Open (Bun-specific)
worker.addEventListener("open", () => {
console.log("Worker ready");
});Note: Messages are auto-queued until ready.
Close (Bun-specific)
worker.addEventListener("close", (event) => {
console.log("Exit code:", event.code);
});Error
worker.onerror = (error) => {
console.error("Worker error:", error);
};Termination
From Main Thread
worker.terminate(); // Force terminateFrom Worker
process.exit(0); // Self-terminateWorkers auto-terminate when event loop is empty.
Lifecycle Management
Keep/Don't Keep Process Alive
// Don't keep main process alive
worker.unref();
// Re-enable keeping alive
worker.ref();
// Set at creation
const worker = new Worker("./worker.ts", {
ref: false,
});Memory Optimization
smol mode reduces memory at cost of performance:
const worker = new Worker("./worker.ts", {
smol: true, // Smaller heap
});Preload Modules
// Load monitoring before worker code
const worker = new Worker("./worker.ts", {
preload: ["./sentry.js", "./datadog.js"],
});
// Single module
const worker = new Worker("./worker.ts", {
preload: "./init.js",
});Environment Data
Share data between threads:
import {
setEnvironmentData,
getEnvironmentData,
} from "worker_threads";
// Main thread
setEnvironmentData("config", { apiUrl: "https://api.example.com" });
// Worker thread
const config = getEnvironmentData("config");Check Thread Context
if (Bun.isMainThread) {
console.log("Main thread");
} else {
console.log("Worker thread");
}Listen for Worker Creation
process.on("worker", (worker) => {
console.log("New worker:", worker.threadId);
});Practical Patterns
Worker Pool
// main.ts
const pool = Array.from({ length: 4 }, () =>
new Worker("./worker.ts")
);
let current = 0;
function dispatch(task: unknown) {
const worker = pool[current++ % pool.length];
return new Promise((resolve) => {
worker.onmessage = (e) => resolve(e.data);
worker.postMessage(task);
});
}
// Usage
const result = await dispatch({ compute: [1, 2, 3] });CPU-Intensive Task
// main.ts
const worker = new Worker("./hash-worker.ts");
worker.postMessage({ data: largeData });
worker.onmessage = (e) => {
console.log("Hash:", e.data.hash);
};
// hash-worker.ts
declare var self: Worker;
self.onmessage = async (event) => {
const hasher = new Bun.CryptoHasher("sha256");
hasher.update(event.data.data);
postMessage({ hash: hasher.digest("hex") });
};Background Processing
// main.ts
const worker = new Worker("./background.ts", {
ref: false, // Don't block shutdown
});
worker.postMessage({ task: "process" });
// background.ts
declare var self: Worker;
self.onmessage = async (event) => {
// Long-running task
await processData(event.data);
process.exit(0);
};Features
| Feature | Supported |
|---|---|
| TypeScript/JSX | ✅ |
| ES Modules | ✅ |
| CommonJS | ✅ |
| postMessage | ✅ |
| Structured Clone | ✅ |
| SharedArrayBuffer | ✅ |
| blob: URLs | ✅ |
| terminate() | ✅ (experimental) |
| ref/unref | ✅ |
| smol mode | ✅ |
| preload | ✅ |