
Bun
- 3 installs
- 19 repo stars
- Updated August 1, 2026
- xobotyi/cc-foundry
Helps with ai & agent building tasks.
About
bun is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- bun
- AI & Agent Building
- AI-coding skill
Bun by the numbers
- 3 all-time installs (skills.sh)
- Ranked #13,677 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/xobotyi/cc-foundry --skill bunAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 3 |
|---|---|
| repo stars | ★ 19 |
| Last updated | August 1, 2026 |
| Repository | xobotyi/cc-foundry ↗ |
What it does
Helps with ai & agent building tasks.
Files
Bun
Use Bun APIs, not Node.js polyfills. If Bun provides a native API for it, use it.
Bun is a batteries-included JavaScript runtime. It replaces Node.js, npm, Jest, and webpack with a single tool. Prefer Bun-native APIs (Bun.serve, Bun.file, Bun.$, bun:sqlite, bun:test) over Node.js equivalents unless portability is an explicit requirement.
References
| Topic | Reference | Contents |
|---|---|---|
| HTTP server | [${CLAUDE_SKILL_DIR}/references/server.md] | Route types, file response patterns, WebSocket pub/sub, server config |
| File I/O and processes | [${CLAUDE_SKILL_DIR}/references/io-and-processes.md] | File I/O details, shell API, child processes, workers |
| Testing | [${CLAUDE_SKILL_DIR}/references/testing.md] | Test modifiers, parametrized tests, mocking, snapshots, CLI flags |
| SQLite, bundler, plugins | [${CLAUDE_SKILL_DIR}/references/ecosystem.md] | SQLite API, bundler options, plugins, macros |
| Configuration | [${CLAUDE_SKILL_DIR}/references/config-and-compat.md] | bunfig.toml sections, Node.js compatibility, env vars |
Prefer Bun-Native APIs
Rule: if Bun.* or bun:* has it, use it. Fall back to node:* only when there's no Bun-native alternative or when portability to Node.js is required.
Core mappings: Bun.serve() over http.createServer(), Bun.file()/Bun.write() over node:fs, Bun.$ over child_process.exec, bun:sqlite over better-sqlite3, bun:test over Jest/Vitest, Bun.password over bcrypt, Bun.sleep() over setTimeout wrappers, Bun.spawn() over child_process.spawn. Use node:fs for directory ops — no Bun API yet. Use Web Streams API over node:stream.
Full API preference table: see ${CLAUDE_SKILL_DIR}/references/io-and-processes.md.
HTTP Server
Routing
- Use `routes` object (v1.2.3+) for declarative path matching. Preferred over
fetch-based routing.
- Route types: exact (
"/users/all", highest priority), parameterized
("/users/:id", req.params.id), wildcard ("/api/*"), per-method ({ GET: handler, POST: handler }).
- Precedence: exact > parameterized > wildcard > global catch-all.
- `fetch` handler as fallback for unmatched routes, not primary routing.
- Always implement `error` handler in
Bun.serve(). - `development: true` in dev for built-in error pages.
Static Responses
- Use static `Response` objects for health checks, redirects, fixed JSON — they are
zero-allocation after init, cached for server lifetime.
- Call `server.reload()` to update static responses at runtime.
Request Object
- Route handlers receive `BunRequest` (extends
Request) withparams(auto
URL-decoded) and cookies (auto-tracked CookieMap).
- TypeScript infers param shape when route is a string literal.
- Cookie changes are auto-tracked —
Set-Cookieheaders added automatically when
using req.cookies.set() / .delete().
WebSocket
- Upgrade via `server.upgrade(req, { data })` in the
fetchhandler. - Use native pub/sub for topic-based broadcasting:
ws.subscribe("topic"),
ws.publish("topic", data).
- Type `ws.data` via the
dataproperty on thewebsockethandler object.
WebSocket limits, server configuration, file response patterns, HTML imports, and server lifecycle details: see ${CLAUDE_SKILL_DIR}/references/server.md.
File I/O
- `Bun.file()` is lazy. Creating a
BunFiledoes not read from disk. It conforms
to Blob.
- Read with `.text()`, `.json()`, `.bytes()`, `.stream()`, `.arrayBuffer()` on
BunFile.
- Check existence:
await file.exists(). Accessfile.sizeandfile.type. - `Bun.write()` handles all types — string, Blob, Response, ArrayBuffer, BunFile.
Uses fastest syscall per platform (copy_file_range, sendfile, clonefile).
- Incremental writing: use
file.writer()(FileSink). Call.flush()to flush
buffer, .end() to flush + close (required to let process exit).
- Built-in stdio references:
Bun.stdin(readonly),Bun.stdout,Bun.stderr. - Use `node:fs` for directory ops —
mkdir,readdir. No Bun-specific API yet. - `import.meta.dir` gives the directory of the current file.
Shell API — Bun.$
Cross-platform bash-like shell with JavaScript interop. Runs in-process (not /bin/sh).
- `$` tagged template for shell commands. Interpolated values are auto-escaped —
injection-safe by default.
- Read output:
.text()(string, auto-quiets),.json()(parsed),.lines()
(async iterator), .blob(), or await $\...\` for { stdout, stderr }` Buffers.
- `.quiet()` to suppress stdout/stderr output.
- Non-zero exit codes throw `ShellError` by default. Use
.nothrow()to handle exit
codes manually. Configure globally: $.nothrow() or $.throws(false).
- Piping and redirection work:
|,>,2>&1,< ${Bun.file("input.txt")},
< ${response}.
- Set environment/cwd:
.env({ FOO: "bar" }),.cwd("/tmp"). Global defaults:
$.env(...), $.cwd(...).
- Security: interpolated variables are escaped (no command injection), but argument
injection is still possible (external commands interpret their own flags). Spawning bash -c bypasses Bun's protections.
Child Processes
- `Bun.spawn()` for fine-grained async process control. Access
proc.pid,
proc.stdout, proc.exited, proc.exitCode. Kill with proc.kill().
- `Bun.spawnSync()` for blocking execution. Rule:
spawnSyncfor CLI tools,
spawn for servers.
- Timeout and abort:
{ timeout: 5000, killSignal: "SIGKILL" }or pass
AbortController.signal.
- IPC between Bun processes:
Bun.spawn(["bun", "child.ts"], { ipc(message) {} }).
Stdin/stdout options, workers, and process details: see ${CLAUDE_SKILL_DIR}/references/io-and-processes.md.
Testing — bun:test
- Import from `bun:test`, not
jestorvitest. - Jest-compatible API:
test,describe,expect,mock,spyOn,beforeAll,
beforeEach, afterEach, afterAll.
- Run with `bun test` — auto-discovers
*.test.*and*.spec.*files. - Cleanup:
mock.restore()restores all spied functions,mock.clearAllMocks()
clears history. Add to afterEach.
- `mock.module("./path", () => ({ ... }))` for module mocking. Works for ESM and CJS.
Test modifiers, parametrized tests, mocking details, snapshots, CLI flags, and bunfig.toml test config: see ${CLAUDE_SKILL_DIR}/references/testing.md.
SQLite, Bundler, Plugins, Macros
- SQLite: use
bun:sqlite— native, synchronous, 3-6x faster thanbetter-sqlite3.
Enable WAL mode. Use prepared statements and transactions.
- Bundler:
Bun.build()with targets"bun","browser","node". Check
result.success and iterate result.logs on failure.
- Plugins:
Bun.plugin()withsetup(build)— extend module resolver and loader.
Register via bunfig.toml preload.
- Macros: compile-time code execution via
{ type: "macro" }import. Return value
inlined; must be JSON-serializable.
Full SQLite API, bundler options, plugin patterns, and macro constraints: see ${CLAUDE_SKILL_DIR}/references/ecosystem.md.
Utilities
Hashing & Passwords
Bun.password.hash(pw)— argon2id default. Also supports"bcrypt".Bun.password.verify(pw, hash)— auto-detects algorithm.Bun.hash("data")— fast non-crypto (Wyhash).new Bun.CryptoHasher("sha256")— crypto hashing.
Sleep & Timing
await Bun.sleep(ms)— async.Bun.sleepSync(ms)— blocking.Bun.nanoseconds()— high-resolution timer.
Comparison & Inspection
Bun.deepEquals(a, b)— deep equality.Bun.deepMatch(subset, obj)— partial match.Bun.inspect(obj)—console.logformat as string.Bun.peek(promise)— read without
awaiting.
Compression
- Gzip:
Bun.gzipSync(data)/Bun.gunzipSync(data). - Deflate:
Bun.deflateSync(data)/Bun.inflateSync(data). - Zstd:
Bun.zstdCompressSync(data)/Bun.zstdDecompressSync(data).
Paths, UUIDs, Streams
Bun.randomUUIDv7()— time-ordered.crypto.randomUUID()— standard v4.- Stream helpers:
Bun.readableStreamToText/JSON/Bytes/Blob/Array/ArrayBuffer(stream). Bun.escapeHTML("<script>"),Bun.stringWidth("hello").
Environment & Metadata
Bun.version,Bun.revision(git hash),Bun.env(alias forprocess.env),
Bun.main (entrypoint path).
import.meta.dir,import.meta.file,import.meta.path— current file info.
HTMLRewriter
- Cloudflare-compatible HTML streaming transformer. Works on
Responseobjects and strings.
Package Manager — bun install
Drop-in replacement for npm/yarn/pnpm. ~25x faster.
- Lockfile:
bun.lock(text, default since v1.2) orbun.lockb(binary). - Does NOT run `postinstall` of dependencies by default (security). Add to
trustedDependencies in package.json to allow.
- Workspaces supported via
package.jsonworkspacesfield. - Auto-install: when no
node_modulesfound, Bun resolves packages on the fly. - `bunx`: execute package binaries without installing (like
npx). - `bun install --production` skips devDependencies.
Configuration & Compatibility
bunfig.toml sections, environment variable loading, and Node.js API compatibility details: see ${CLAUDE_SKILL_DIR}/references/config-and-compat.md.
Application
When writing Bun code:
- Apply all conventions silently — don't narrate each rule being followed.
- If an existing codebase uses Node.js patterns, follow codebase style but flag
that Bun-native alternatives exist.
- For new projects, use Bun-native APIs throughout.
When reviewing Bun code:
- Cite the specific Node.js-to-Bun migration and show the fix inline.
- Don't lecture — state what's suboptimal and how to fix it.
Integration
The javascript skill governs language choices; this skill governs Bun runtime and toolchain decisions. Activate typescript alongside both when working with TypeScript.
Use Bun APIs, not Node.js polyfills. When in doubt, check if Bun has a native API.
{
"sources": {
"Bun LLMs Full Documentation": "https://bun.sh/docs/llms-full.txt",
"Bun APIs Overview": "https://bun.com/docs/runtime/bun-apis.md",
"Bun HTTP Server (Bun.serve)": "https://bun.com/docs/runtime/http/server.md",
"Bun Shell API (Bun.$)": "https://bun.com/docs/runtime/shell.md",
"Bun File I/O (Bun.file, Bun.write)": "https://bun.com/docs/runtime/file-io.md",
"Bun Child Process (Bun.spawn)": "https://bun.com/docs/runtime/child-process.md",
"Bun Bundler": "https://bun.com/docs/bundler/index.md",
"Bun Macros": "https://bun.com/docs/bundler/macros.md",
"Bun Test Runner": "https://bun.com/docs/test/index.md",
"Bun Test Writing": "https://bun.com/docs/test/writing-tests.md",
"Bun Test Mocks": "https://bun.com/docs/test/mocks.md",
"Bun Configuration (bunfig.toml)": "https://bun.com/docs/runtime/bunfig.md",
"Bun Node.js Compatibility": "https://bun.com/docs/runtime/nodejs-compat.md",
"Bun Package Manager (bun install)": "https://bun.com/docs/pm/cli/install.md",
"Bun SQLite": "https://bun.com/docs/runtime/sqlite.md",
"Bun Workers": "https://bun.com/docs/runtime/workers.md",
"Bun WebSockets": "https://bun.com/docs/runtime/http/websockets.md",
"Bun Environment Variables": "https://bun.com/docs/runtime/environment-variables.md",
"Bun Plugins": "https://bun.com/docs/runtime/plugins.md",
"Bun Fullstack Dev Server": "https://bun.com/docs/bundler/fullstack.md"
},
"lastFetched": "2026-02-16T13:19:42.784Z"
}
Configuration and Node.js Compatibility
bunfig.toml
Optional Bun-specific configuration. Bun works without it — uses package.json and tsconfig.json by default. Place in project root alongside package.json.
Runtime Config
# Scripts to run before any file execution
preload = ["./preload.ts"]
# JSX config (also configurable in tsconfig.json)
jsx = "react"
jsxImportSource = "react"
# Reduce memory at cost of performance
smol = true
# Log level
logLevel = "debug" # "debug" | "warn" | "error"
# Replace globals at compile time
[define]
"process.env.NODE_ENV" = "'production'"
# Custom file extension loaders
[loader]
".yaml" = "text"
# Disable automatic .env file loading
[env]
file = falseTest Config
[test]
preload = ["./setup.ts"]
root = "./__tests__"
coverage = true
coverageThreshold = { line = 0.8, function = 0.8 }
coverageReporter = ["text", "lcov"]
coverageDir = "coverage"
coverageSkipTestFiles = false
retry = 3
timeout = 10000
randomize = false
[test.reporter]
dots = false
junit = "test-results.xml"Package Manager Config
[install]
exact = false # use caret ranges by default
production = false # install devDependencies
frozenLockfile = false # allow lockfile updates
saveTextLockfile = true # text bun.lock (default since v1.2)
auto = "auto" # auto-install when no node_modules
linker = "hoisted" # "hoisted" | "isolated"
registry = "https://registry.npmjs.org"
[install.scopes]
myorg = { token = "$NPM_TOKEN", url = "https://registry.myorg.com/" }
[install.cache]
dir = "~/.bun/install/cache"
disable = falseRun Config
[run]
shell = "bun" # "bun" | "system" — Bun shell or OS shell for scripts
bun = true # auto-alias `node` to `bun` in scripts
silent = true # suppress "Running..." messagesGlobal Config
Global config at $HOME/.bunfig.toml or $XDG_CONFIG_HOME/.bunfig.toml. Local overrides global (shallow merge). CLI flags override both.
Package Manager — bun install
Drop-in replacement for npm/yarn/pnpm. ~25x faster.
Commands
bun install # install all dependencies
bun install react # add dependency
bun install -d typescript # add dev dependency
bun install --production # skip devDependencies
bun add react # alias for bun install
bun remove react # remove dependency
bun update # update dependencies
bun pm ls # list installed packagesKey Behaviors
- Lockfile:
bun.lock(text, default since v1.2) orbun.lockb(binary) - Lifecycle scripts: Does NOT run
postinstallof dependencies by default
(security). Add to trustedDependencies in package.json to allow.
- Workspaces: Supported via
package.jsonworkspacesfield - Auto-install: When no
node_modulesfound, Bun resolves packages on the fly - `bunx`: Execute package binaries without installing (like
npx)
trustedDependencies
{
"trustedDependencies": ["esbuild", "sharp"]
}Required for packages with postinstall scripts.
Environment Variables
Bun auto-loads .env files in this order:
1. .env.local (always, unless NODE_ENV=test) 2. .env.development / .env.production / .env.test (per NODE_ENV) 3. .env
Access via process.env or Bun.env:
process.env.API_KEY; // standard Node.js way
Bun.env.API_KEY; // Bun alias (same object)Disable auto-loading: bunfig.toml env = false or --env-file="".
Explicitly load specific files: --env-file=.env.staging.
Node.js Compatibility
Bun aims for 100% Node.js API compatibility. If a Node.js package doesn't work in Bun, it's considered a bug.
Fully Compatible (use freely)
node:assert, node:buffer, node:console, node:dgram, node:dns, node:events, node:fs, node:http, node:net, node:os, node:path, node:querystring, node:readline, node:stream, node:string_decoder, node:timers, node:tty, node:url, node:zlib
Mostly Compatible (minor gaps)
| Module | Notes |
|---|---|
node:async_hooks | AsyncLocalStorage works. executionAsyncId is a stub. |
node:child_process | Missing proc.gid/proc.uid. No socket handle IPC. |
node:crypto | Missing secureHeapUsed, setEngine, setFips |
node:http2 | Client & server work. Missing allowHTTP1, pushStream. |
node:https | APIs work, Agent not always used. |
node:tls | Missing tls.createSecurePair |
node:vm | Core works. Missing vm.measureMemory. |
node:worker_threads | Missing some Worker options (stdin, stdout). |
process | process.binding partially. process.title is no-op on macOS/Linux. |
Not Implemented
node:repl, node:sqlite, node:trace_events
Web APIs
All standard Web APIs are available globally: fetch, Request, Response, Headers, URL, URLSearchParams, WebSocket, Crypto, TextEncoder, TextDecoder, ReadableStream, WritableStream, AbortController, structuredClone, FormData, Blob, Event, EventTarget, BroadcastChannel, MessageChannel, MessagePort, etc.
Globals Available
__dirname,__filename— work in ESM (Bun extension)require()— works in ESM (Bun extension)import.meta.dir,import.meta.file,import.meta.path— Bun-specificimport.meta.require()— require from ESM context
When to Use Node.js APIs vs Bun APIs
| Task | Prefer | Reason |
|---|---|---|
| HTTP server | Bun.serve() | Faster, declarative routing |
| File read/write | Bun.file() / Bun.write() | Optimized syscalls |
| Shell commands | Bun.$ | Cross-platform, injection-safe |
| Process spawn | Bun.spawn() | Better API, faster |
| SQLite | bun:sqlite | Native, 3-6x faster |
| Testing | bun:test | Built-in, Jest-compatible |
| Password hashing | Bun.password | Built-in argon2id/bcrypt |
| Directory ops | node:fs | No Bun API yet |
| TCP/UDP sockets | Bun.listen / Bun.connect | Native, faster |
| Streams | Web Streams API | Standard, Bun-optimized |
| Crypto (general) | node:crypto / Web Crypto | Both work |
Rule: if Bun.* or bun:* has it, use it. Fall back to node:* only when there's no Bun-native alternative or when portability to Node.js is required.
Bun Ecosystem APIs
SQLite, WebSockets, bundler, plugins, and utilities.
SQLite — bun:sqlite
Native high-performance SQLite3 driver. Synchronous API. 3-6x faster than better-sqlite3.
Database
import { Database } from "bun:sqlite";
const db = new Database("mydb.sqlite"); // file-based
const db = new Database(":memory:"); // in-memory
const db = new Database("mydb.sqlite", { readonly: true });
const db = new Database("mydb.sqlite", { create: true });
const db = new Database("mydb.sqlite", { strict: true }); // throw on missing paramsQueries
// Prepared statements (preferred — cached and reusable)
const query = db.query("SELECT * FROM users WHERE id = $id");
query.get({ $id: 1 }); // single row or null
query.all({ $id: 1 }); // array of rows
query.run({ $id: 1 }); // execute, return changes info
query.values({ $id: 1 }); // array of arrays (no column names)
// Map results to class instances (no ORM needed)
class User { id!: number; name!: string; }
query.as(User).get({ $id: 1 }); // returns User instanceExec and Run
db.exec("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)");
db.run("INSERT INTO users (name) VALUES (?)", ["Alice"]);
// Multi-statement in single call
db.run("INSERT INTO t VALUES (1); INSERT INTO t VALUES (2);");Transactions
const insert = db.prepare("INSERT INTO users (name) VALUES ($name)");
const insertMany = db.transaction((users: { name: string }[]) => {
for (const user of users) insert.run(user);
});
insertMany([{ name: "Alice" }, { name: "Bob" }]);Transactions auto-rollback on exception. Nest transactions for savepoints.
Parameters
Named ($name, :name, @name) and positional (?):
db.query("SELECT * FROM users WHERE id = ?").get(1);
db.query("SELECT * FROM users WHERE id = $id").get({ $id: 1 });With strict: true, parameter prefix is optional and missing params throw.
Features
BLOB→Uint8Arrayautomatic conversionbigintsupport via{ safeIntegers: true }on statements- WAL mode:
db.exec("PRAGMA journal_mode = WAL") - Close:
db.close()— also supportsusing/Symbol.dispose
Bundler — Bun.build()
const result = await Bun.build({
entrypoints: ["./src/index.ts"],
outdir: "./dist",
target: "bun", // "bun" | "browser" | "node"
format: "esm", // "esm" | "cjs" | "iife"
minify: true, // or { whitespace, identifiers, syntax }
splitting: true, // code splitting
sourcemap: "external", // "none" | "inline" | "external" | "linked"
define: { "process.env.NODE_ENV": JSON.stringify("production") },
external: ["express"], // don't bundle these
naming: "[dir]/[name]-[hash].[ext]",
});
if (!result.success) {
for (const msg of result.logs) console.error(msg);
}Targets
| Target | Use case |
|---|---|
"bun" | Bun server apps. Inlines bun:* imports. |
"browser" | Client-side. Standard web APIs only. |
"node" | Node.js apps. Preserves node:* imports. |
HTML Entrypoints
await Bun.build({
entrypoints: ["./index.html"],
outdir: "./dist",
});Processes <script>, <link>, and <img> tags. Supports CSS, TypeScript, Tailwind.
Plugins — Bun.plugin()
Extend Bun's module resolver and loader:
import { plugin } from "bun";
plugin({
name: "yaml-loader",
setup(build) {
build.onLoad({ filter: /\.yaml$/ }, async (args) => {
const text = await Bun.file(args.path).text();
return { contents: `export default ${JSON.stringify(parse(text))}` };
});
},
});Register plugins via bunfig.toml:
preload = ["./plugins/yaml.ts"]Macros
Compile-time code execution. Functions imported with { type: "macro" } run at bundle time and their return value is inlined:
// macro.ts
export function getVersion() {
return "1.0.0";
}
// app.ts
import { getVersion } from "./macro" with { type: "macro" };
const version = getVersion(); // inlined as "1.0.0" at build timeMacros can only return serializable values (JSON-compatible, Response, Blob).
Hashing & Passwords
Password Hashing
const hash = await Bun.password.hash("password"); // argon2id default
const hash = await Bun.password.hash("password", "bcrypt");
const valid = await Bun.password.verify("password", hash);General Hashing
Bun.hash("data"); // fast non-crypto hash (Wyhash)
const hasher = new Bun.CryptoHasher("sha256");
hasher.update("data");
hasher.digest("hex"); // crypto hashUtilities
Sleep & Timing
await Bun.sleep(1000); // async sleep (ms)
Bun.sleepSync(1000); // blocking sleep (ms)
Bun.nanoseconds(); // high-resolution timerUUID
Bun.randomUUIDv7(); // time-ordered UUID v7
crypto.randomUUID(); // standard UUID v4Comparison & Inspection
Bun.deepEquals(a, b); // deep equality check
Bun.deepMatch(subset, obj); // partial deep match
Bun.inspect(obj); // like console.log format, returns string
Bun.peek(promise); // read promise value without awaitingString & HTML
Bun.escapeHTML("<script>"); // <script>
Bun.stringWidth("hello"); // terminal column widthCompression
Bun.gzipSync(data); // gzip compress
Bun.gunzipSync(data); // gzip decompress
Bun.deflateSync(data); // deflate compress
Bun.inflateSync(data); // deflate decompress
Bun.zstdCompressSync(data); // zstd compress
Bun.zstdDecompressSync(data); // zstd decompressPaths & URLs
Bun.fileURLToPath(url); // file:// URL to path
Bun.pathToFileURL(path); // path to file:// URL
Bun.which("node"); // resolve executable in PATH
Bun.resolveSync("./mod", dir); // resolve module specifierStream Helpers
await Bun.readableStreamToText(stream);
await Bun.readableStreamToJSON(stream);
await Bun.readableStreamToBytes(stream);
await Bun.readableStreamToBlob(stream);
await Bun.readableStreamToArray(stream);
await Bun.readableStreamToArrayBuffer(stream);Environment & Metadata
Bun.version; // Bun version string
Bun.revision; // Git commit hash
Bun.env; // alias for process.env
Bun.main; // path to entrypoint file
import.meta.dir; // directory of current file
import.meta.file; // filename of current file
import.meta.path; // full path of current fileHTMLRewriter
Cloudflare-compatible HTML streaming transformer:
const rewriter = new HTMLRewriter()
.on("a", {
element(el) {
el.setAttribute("target", "_blank");
},
})
.transform(response);Works on Response objects and strings. Useful for HTML post-processing.
File I/O, Shell, and Processes
File I/O
Reading Files — Bun.file()
Creates a lazy BunFile reference (conforms to Blob). No disk read until content is accessed.
const file = Bun.file("data.json");
file.size; // number of bytes
file.type; // MIME type
await file.exists(); // boolean
await file.text(); // string
await file.json(); // parsed JSON
await file.bytes(); // Uint8Array
await file.arrayBuffer(); // ArrayBuffer
file.stream(); // ReadableStreamCreate from file descriptor or URL:
Bun.file(1234); // fd
Bun.file(new URL(import.meta.url)); // current fileBuilt-in stdio references:
Bun.stdin; // readonly BunFile
Bun.stdout; // BunFile
Bun.stderr; // BunFileDelete a file:
await Bun.file("logs.json").delete();Writing Files — Bun.write()
Multi-tool for writing. Accepts string, Blob, ArrayBuffer, TypedArray, Response. Uses fastest syscall per platform (copy_file_range, sendfile, clonefile).
await Bun.write("output.txt", "hello"); // string
await Bun.write("copy.txt", Bun.file("original.txt")); // file-to-file
await Bun.write("index.html", await fetch("https://x.com")); // Response
await Bun.write(Bun.stdout, Bun.file("data.txt")); // to stdoutIncremental Writing — FileSink
const writer = Bun.file("output.txt").writer();
writer.write("chunk 1\n");
writer.write("chunk 2\n");
writer.flush(); // flush buffer to disk
writer.end(); // flush + close (required to let process exit)Configure buffer size: file.writer({ highWaterMark: 1024 * 1024 }).
Call writer.unref() to avoid keeping the process alive.
Directory Operations
No Bun-specific API — use node:fs:
import { readdir, mkdir } from "node:fs/promises";
const files = await readdir(".", { recursive: true });
await mkdir("path/to/dir", { recursive: true });import.meta.dir gives the directory of the current file.
Shell API — Bun.$
Cross-platform bash-like shell with JavaScript interop. Runs in-process (not /bin/sh). Interpolated values are auto-escaped — injection-safe by default.
Basic Usage
import { $ } from "bun";
await $`echo "Hello"`; // prints to stdout
const result = await $`echo "Hello"`.text(); // capture as string
const data = await $`echo '{"a":1}'`.json(); // capture as JSON
const { stdout, stderr } = await $`cmd`.quiet(); // suppress outputReading Output
| Method | Returns |
|---|---|
.text() | string (auto-quiets) |
.json() | Parsed JSON |
.lines() | Async iterator of lines |
.blob() | Blob |
await $\...\`` | { stdout: Buffer, stderr: Buffer } |
Error Handling
Non-zero exit codes throw ShellError by default:
try {
await $`failing-command`.text();
} catch (err) {
console.log(err.exitCode, err.stderr.toString());
}Disable throwing: .nothrow() — then check exitCode manually.
Configure globally: $.nothrow() or $.throws(false).
Piping and Redirection
await $`echo "hello" | wc -w`; // piping
await $`echo "data" > output.txt`; // redirect stdout to file
await $`cmd 2>&1`; // stderr to stdout
await $`cat < ${Bun.file("input.txt")}`; // file as stdin
await $`cat < ${response}`; // Response body as stdinJavaScript objects as redirect targets: Buffer, Uint8Array, Bun.file(), Response.
Environment and Working Directory
await $`echo $FOO`.env({ ...process.env, FOO: "bar" });
await $`pwd`.cwd("/tmp");
// Global defaults
$.env({ FOO: "bar" });
$.cwd("/tmp");Built-in Commands
Cross-platform: cd, ls, rm, echo, pwd, cat, touch, mkdir, which, mv, exit, true, false, yes, seq, dirname, basename.
Security
- Interpolated variables are escaped — no command injection
- Argument injection is still possible (external commands interpret their own flags)
- Spawning a new shell (
bash -c) bypasses Bun's protections
Child Processes — Bun.spawn()
Async API
const proc = Bun.spawn(["bun", "--version"], {
cwd: "./subdir",
env: { ...process.env, FOO: "bar" },
onExit(proc, exitCode, signalCode, error) { /* ... */ },
});
proc.pid; // process ID
const output = await proc.stdout.text(); // read stdout
await proc.exited; // wait for exit
proc.exitCode; // number | null
proc.kill(); // SIGTERM
proc.kill("SIGKILL"); // specific signal
proc.unref(); // detach from parentstdin Options
| Value | Description |
|---|---|
null | No input (default) |
"pipe" | Returns FileSink for writing |
"inherit" | Inherit parent stdin |
Bun.file() | Read from file |
ReadableStream | Pipe stream |
Response | Use response body |
stdout/stderr Options
| Value | Description |
|---|---|
"pipe" | Default stdout — ReadableStream |
"inherit" | Default stderr — inherit parent |
"ignore" | Discard |
Bun.file() | Write to file |
AbortSignal and Timeout
const controller = new AbortController();
const proc = Bun.spawn({ cmd: ["sleep", "100"], signal: controller.signal });
controller.abort();
// Auto-kill after timeout
Bun.spawn({ cmd: ["sleep", "10"], timeout: 5000, killSignal: "SIGKILL" });IPC Between Bun Processes
// parent.ts
const child = Bun.spawn(["bun", "child.ts"], {
ipc(message, child) { child.send("reply"); },
});
child.send("hello");
// child.ts
process.on("message", msg => console.log(msg));
process.send("from child");For Bun-Node IPC, use serialization: "json".
Sync API — Bun.spawnSync()
Blocking. Returns { stdout: Buffer, stderr: Buffer, exitCode, success }.
const { stdout, success } = Bun.spawnSync(["echo", "hello"]);Rule of thumb: spawnSync for CLI tools, spawn for servers.
Workers
const worker = new Worker(new URL("worker.ts", import.meta.url));
worker.postMessage("ping");
worker.onmessage = (event) => console.log(event.data);Workers run in separate threads. Use postMessage / onmessage for communication. Supports structuredClone for data transfer.
Bun HTTP Server
Bun.serve() — high-performance HTTP server built on uWebSockets.
Basic Setup
const server = Bun.serve({
routes: {
"/api/status": new Response("OK"), // static (zero-alloc)
"/users/:id": req => new Response(`User ${req.params.id}`),
"/api/posts": {
GET: () => Response.json({ posts: [] }),
POST: async req => Response.json(await req.json(), { status: 201 }),
},
"/api/*": Response.json({ error: "Not found" }, { status: 404 }),
},
fetch(req) {
return new Response("Not Found", { status: 404 }); // fallback
},
error(error) {
return new Response("Internal Server Error", { status: 500 });
},
});Route Types
Declarative routes (v1.2.3+)
Preferred over fetch-based routing. Supports:
- Exact:
"/users/all"— highest priority - Parameterized:
"/users/:id"—req.params.id - Wildcard:
"/api/*"— catch-all within prefix - Per-method:
{ GET: handler, POST: handler }
Precedence: exact > parameterized > wildcard > global catch-all.
Static Responses
Zero-allocation after init. Use for health checks, redirects, fixed JSON:
routes: {
"/health": new Response("OK"),
"/redirect": Response.redirect("https://example.com"),
"/config": Response.json({ version: "1.0.0" }),
}Cached for server lifetime. Call server.reload() to update.
File Responses
Two modes with different tradeoffs:
| Pattern | Behavior | Best for |
|---|---|---|
new Response(await file.bytes()) | Buffered in memory, ETag support, zero I/O | Small static assets |
new Response(Bun.file(path)) | Per-request read, 404 handling, Range support | Large/dynamic files |
Bun uses sendfile(2) for zero-copy file transfers when possible.
HTML Imports (Fullstack)
import app from "./index.html";
Bun.serve({ routes: { "/": app } });- Dev (
bun --hot): On-demand bundling with HMR - Prod (
bun build --target=bun): Pre-built manifest, zero runtime bundling
Supports React, TypeScript, Tailwind CSS out of the box.
Request Object
Route handlers receive BunRequest (extends Request):
interface BunRequest extends Request {
params: Record<string, string>; // route parameters (auto URL-decoded)
readonly cookies: CookieMap; // cookie access
}Type-safe params when route is a string literal — TypeScript infers param shape.
Cookies
Bun.serve() with routes auto-tracks cookie changes:
"/login": req => {
req.cookies.set("session", token, { httpOnly: true, secure: true, maxAge: 86400 });
return new Response("OK"); // Set-Cookie header added automatically
},
"/logout": req => {
req.cookies.delete("session");
return new Response("OK"); // maxAge=0 cookie sent automatically
},WebSocket Upgrade
Bun.serve({
fetch(req, server) {
if (server.upgrade(req, { data: { userId: "123" } })) return;
return new Response("Upgrade failed", { status: 500 });
},
websocket: {
open(ws) { ws.subscribe("chat"); },
message(ws, msg) { ws.publish("chat", `${ws.data.userId}: ${msg}`); },
close(ws) { ws.unsubscribe("chat"); },
},
});Type ws.data with the data property on the websocket handler:
websocket: {
data: {} as { userId: string }, // types ws.data across all hooks
message(ws, msg) { /* ws.data.userId is string */ },
},WebSocket Config
| Option | Default | Description |
|---|---|---|
maxPayloadLength | 16 MB | Max message size |
idleTimeout | 120s | Idle disconnect time |
backpressureLimit | 1 MB | Queue limit before backpressure |
perMessageDeflate | false | Per-message compression |
publishToSelf | false | Receive own published messages |
Pub/Sub
Native topic-based broadcasting:
ws.subscribe("topic");
ws.publish("topic", data); // to all except sender
server.publish("topic", data); // to all subscribers
ws.unsubscribe("topic");
server.subscriberCount("topic");Server Configuration
Bun.serve({
port: 3000, // default: $BUN_PORT, $PORT, $NODE_PORT, or 3000
hostname: "0.0.0.0", // default
idleTimeout: 10, // seconds, connection idle limit
development: true, // built-in error pages
tls: { key: Bun.file("key.pem"), cert: Bun.file("cert.pem") },
unix: "/tmp/my.sock", // Unix domain socket (instead of port)
});Port 0 selects a random available port — read server.port after.
Export Default Syntax
export default {
fetch(req) { return new Response("OK"); },
} satisfies Serve.Options;Bun auto-wraps in Bun.serve() when file has default export with fetch.
Server Lifecycle
server.reload({ routes: { ... } }); // hot-swap routes (fetch, error, routes only)
await server.stop(); // graceful shutdown
await server.stop(true); // force-close all connections
server.unref(); // don't keep process alive
server.ref(); // keep process alive (default)Per-Request Controls
server.timeout(req, 60); // custom timeout (seconds), 0 to disable
server.requestIP(req); // { address, port } or nullMetrics
server.pendingRequests; // in-flight HTTP requests
server.pendingWebSockets; // active WebSocket connections
server.subscriberCount(topic); // subscribers for a topicBun Testing
Built-in Jest-compatible test runner. Import from bun:test.
Running Tests
bun test # all test files
bun test ./specific.test.ts # specific file (use ./ prefix)
bun test foo bar # files matching "foo" or "bar"
bun test -t "pattern" # filter by test name regex
bun test --watch # re-run on changes
bun test --coverage # generate coverage reportAuto-discovers: *.test.{js,jsx,ts,tsx}, *_test.*, *.spec.*, *_spec.*.
Key CLI Flags
| Flag | Default | Description |
|---|---|---|
--timeout | 5000 | Per-test timeout (ms) |
--bail | - | Stop after N failures |
--retry | 0 | Retry failed tests N times |
--concurrent | false | Run tests in parallel |
--max-concurrency | 20 | Max parallel tests |
--rerun-each | 0 | Run each test N extra times |
--randomize | false | Random execution order |
--seed | - | Reproducible random order |
--update-snapshots | false | Update snapshot files |
--preload | - | Scripts to run before tests |
--reporter=junit | - | JUnit XML output (needs --reporter-outfile) |
AI Agent Mode
Set CLAUDECODE=1 or AGENT=1 to suppress passing test output — only failures shown.
Writing Tests
import { test, expect, describe } from "bun:test";
test("basic", () => {
expect(2 + 2).toBe(4);
});
describe("group", () => {
test("nested", async () => {
const result = await Promise.resolve(42);
expect(result).toBe(42);
});
});Timeouts
test("slow op", async () => { /* ... */ }, 500); // 500ms timeoutTimed-out tests throw uncatchable exceptions. Child processes spawned in the test are auto-killed.
Test Modifiers
| Modifier | Effect |
|---|---|
test.skip(name, fn) | Skip this test |
test.todo(name, fn) | Mark as TODO (not run) |
test.only(name, fn) | Run only this test (needs --only) |
test.if(cond)(name, fn) | Run if condition is truthy |
test.skipIf(cond)(name, fn) | Skip if condition is truthy |
test.todoIf(cond)(name, fn) | TODO if condition is truthy |
test.failing(name, fn) | Pass if test fails, fail if it passes |
test.concurrent(name, fn) | Run concurrently (even without --concurrent) |
test.serial(name, fn) | Force sequential (even with --concurrent) |
Modifiers chain: test.failing.each([...])("name %d", fn).
Parametrized Tests
test.each([
[1, 2, 3],
[4, 5, 9],
])("%p + %p = %p", (a, b, expected) => {
expect(a + b).toBe(expected);
});
// Object form — single argument
test.each([
{ a: 1, b: 2, expected: 3 },
])("add($a, $b) = $expected", ({ a, b, expected }) => {
expect(a + b).toBe(expected);
});Format specifiers: %p (pretty), %s (string), %d (number), %i (int), %j (JSON), %# (index).
describe.each also works for parametrized suites.
Retries and Repeats
test("flaky", async () => { /* ... */ }, { retry: 3 }); // retry up to 3x
test("stress", () => { /* ... */ }, { repeats: 20 }); // run 21 times totalCannot combine retry and repeats on the same test.
Assertion Counting
test("async assertions", async () => {
expect.hasAssertions(); // at least one assertion must run
expect.assertions(2); // exactly 2 assertions must run
// ...
});Lifecycle Hooks
import { beforeAll, beforeEach, afterEach, afterAll } from "bun:test";
beforeAll(() => { /* once before all tests */ });
beforeEach(() => { /* before each test */ });
afterEach(() => { /* after each test */ });
afterAll(() => { /* once after all tests */ });Can also be defined in --preload scripts for global setup.
Matchers
Full Jest matcher compatibility. Key matchers:
Equality: .toBe(), .toEqual(), .toStrictEqual() Truthiness: .toBeTruthy(), .toBeFalsy(), .toBeNull(), .toBeUndefined(), .toBeDefined(), .toBeNaN() Numbers: .toBeGreaterThan(), .toBeLessThan(), .toBeCloseTo() Strings/Arrays: .toContain(), .toHaveLength(), .toMatch() Objects: .toHaveProperty(), .toMatchObject() Errors: .toThrow(), .toBeInstanceOf() Promises: .resolves, .rejects Mocks: .toHaveBeenCalled(), .toHaveBeenCalledWith(), .toHaveBeenCalledTimes() Snapshots: .toMatchSnapshot(), .toMatchInlineSnapshot()
All support .not inversion.
Mocks
Function Mocks
import { test, expect, mock } from "bun:test";
const fn = mock(() => 42);
fn(1, 2);
expect(fn).toHaveBeenCalledWith(1, 2);
fn.mock.calls; // [[1, 2]]
fn.mock.results; // [{ type: "return", value: 42 }]jest.fn() is an alias for mock().
Mock Methods
| Method | Description |
|---|---|
.mockImplementation(fn) | Set implementation |
.mockImplementationOnce(fn) | Set for next call only |
.mockReturnValue(val) | Set return value |
.mockReturnValueOnce(val) | Set for next call only |
.mockResolvedValue(val) | Set resolved promise value |
.mockRejectedValue(val) | Set rejected promise value |
.mockClear() | Clear call history |
.mockReset() | Clear history + remove implementation |
.mockRestore() | Restore original implementation |
Spies
import { spyOn } from "bun:test";
const spy = spyOn(object, "method");
object.method();
expect(spy).toHaveBeenCalled();
spy.mockImplementation(() => "mocked"); // override behaviorModule Mocks
import { mock } from "bun:test";
mock.module("./api-client", () => ({
fetchUser: mock(async (id: string) => ({ id, name: "Mock" })),
}));Key behaviors:
- Works for ESM and CJS
- Updates live bindings — existing imports see the mock
- Supports relative paths, absolute paths, and package names
- Use
--preloadto mock before any imports (prevents side effects)
Global Mock Cleanup
mock.restore(); // restore all spied functions
mock.clearAllMocks(); // clear call history for all mocksCommon pattern — add to afterEach:
afterEach(() => {
mock.restore();
mock.clearAllMocks();
});Vitest Compatibility
vi is available as an alias: vi.fn(), vi.spyOn(), vi.mock().
Snapshot Testing
expect({ a: 1 }).toMatchSnapshot();
expect(value).toMatchInlineSnapshot(`"expected"`);Update snapshots: bun test --update-snapshots (or -u).
Type Testing
import { expectTypeOf } from "bun:test";
expectTypeOf<string>().toEqualTypeOf<string>();
expectTypeOf(fn).parameters.toEqualTypeOf<[string]>();
expectTypeOf(fn).returns.toEqualTypeOf<number>();Type assertions are no-ops at runtime — run bunx tsc --noEmit to verify.
Configuration via bunfig.toml
[test]
preload = ["./setup.ts"]
root = "./__tests__"
coverage = true
coverageThreshold = { line = 0.8, function = 0.8 }
coverageReporter = ["text", "lcov"]
retry = 3
timeout = 10000