| name | bun-fieldguide |
| description | This skill should be used when working with Bun runtime, bun:sqlite, Bun.serve, bun:test, or when Bun, bun:test, or Bun-specific patterns are mentioned. |
| metadata | {"version":"1.0.1"} |
Bun Development
Bun runtime → native APIs → zero-dependency patterns.
<when_to_use>
- Bun runtime development
- SQLite database with bun:sqlite
- HTTP server with Bun.serve
- Testing with bun:test
- File operations with Bun.file/Bun.write
- Shell operations with $ template
- Password hashing with Bun.password
- Environment variable handling
- Building and bundling
NOT for: Node.js-only patterns, cross-runtime libraries, non-Bun projects
</when_to_use>
<runtime_basics>
Package management:
bun install
bun add zod
bun remove zod
bun update
Script execution:
bun run dev
bun run src/index.ts
bun --watch index.ts
Testing:
bun test
bun test src/
bun test --watch
bun test --coverage
Building:
bun build ./index.ts --outfile dist/bundle.js
bun build ./index.ts --compile --outfile myapp
</runtime_basics>
File Operations
<file_operations>
const file = Bun.file("./data.json");
if (!(await file.exists())) throw new Error("File not found");
const text = await file.text();
const json = await file.json();
const buffer = await file.arrayBuffer();
const stream = file.stream();
console.log(file.size, file.type);
await Bun.write("./output.txt", "content");
await Bun.write("./data.json", JSON.stringify(data));
await Bun.write("./blob.txt", new Blob(["data"]));
</file_operations>
SQLite (bun:sqlite)
import { Database } from "bun:sqlite";
const db = new Database("app.db", {
create: true,
readwrite: true,
strict: true,
});
db.run(`
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY,
email TEXT UNIQUE NOT NULL,
name TEXT NOT NULL,
created_at TEXT DEFAULT CURRENT_TIMESTAMP
)
`);
const getUser = db.prepare("SELECT * FROM users WHERE id = ?");
const createUser = db.prepare(
"INSERT INTO users (id, email, name) VALUES (?, ?, ?) RETURNING *"
);
const user = getUser.get("user-123");
const all = db.prepare("SELECT * FROM users").all();
db.prepare("DELETE FROM users WHERE id = ?").run("id");
const stmt = db.prepare("SELECT * FROM users WHERE email = $email");
stmt.get({ $email: "alice@example.com" });
const transfer = db.transaction(
(fromId: string, toId: string, amount: number) => {
db.run("UPDATE accounts SET balance = balance - ? WHERE id = ?", [
amount,
fromId,
]);
db.run("UPDATE accounts SET balance = balance + ? WHERE id = ?", [
amount,
toId,
]);
}
);
transfer("alice", "bob", 100);
db.close();
See sqlite-patterns.md for migrations, pooling, repository pattern.
Password Hashing
const hash = await Bun.password.hash("password123", {
algorithm: "argon2id",
memoryCost: 65536,
timeCost: 3,
});
const bcryptHash = await Bun.password.hash("password123", {
algorithm: "bcrypt",
cost: 12,
});
const isValid = await Bun.password.verify("password123", hash);
if (!isValid) throw new Error("Invalid password");
Auth flow example:
app.post("/auth/register", zValidator("json", RegisterSchema), async (c) => {
const { email, password } = c.req.valid("json");
const db = c.get("db");
if (db.prepare("SELECT id FROM users WHERE email = ?").get(email)) {
throw new HTTPException(409, { message: "Email already registered" });
}
const hashedPassword = await Bun.password.hash(password, {
algorithm: "argon2id",
});
const user = db
.prepare(
`
INSERT INTO users (id, email, password) VALUES (?, ?, ?) RETURNING id, email
`
)
.get(crypto.randomUUID(), email, hashedPassword);
return c.json({ user }, 201);
});
HTTP Server
<http_server>
Bun.serve({
port: 3000,
fetch(req) {
const url = new URL(req.url);
if (url.pathname === "/") return new Response("Hello");
if (url.pathname === "/json") return Response.json({ ok: true });
return new Response("Not found", { status: 404 });
},
error(err) {
return new Response(`Error: ${err.message}`, { status: 500 });
},
});
With Hono (recommended for APIs):
import { Hono } from "hono";
const app = new Hono()
.get("/", (c) => c.text("Hello"))
.get("/json", (c) => c.json({ ok: true }));
Bun.serve({ port: 3000, fetch: app.fetch });
See server-patterns.md for routing, middleware, file serving, streaming.
</http_server>
WebSocket
import type { ServerWebSocket } from "bun";
type WsData = { userId: string };
Bun.serve<WsData>({
port: 3000,
fetch(req, server) {
const url = new URL(req.url);
if (url.pathname === "/ws") {
const userId = url.searchParams.get("userId") || "anon";
return server.upgrade(req, { data: { userId } })
? undefined
: new Response("Upgrade failed", { status: 400 });
}
return new Response("Hello");
},
websocket: {
open(ws: ServerWebSocket<WsData>) {
ws.subscribe("chat");
ws.send(JSON.stringify({ type: "connected" }));
},
message(ws: ServerWebSocket<WsData>, msg: string | Buffer) {
ws.publish("chat", msg);
},
close(ws: ServerWebSocket<WsData>) {
ws.unsubscribe("chat");
},
},
});
See server-patterns.md for client tracking, rooms, reconnection.
Shell Operations
import { $ } from "bun";
const result = await $`ls -la`;
console.log(result.text());
const dir = "./src";
await $`find ${dir} -name "*.ts"`;
const { exitCode } = await $`npm test`.nothrow();
if (exitCode !== 0) console.error("Tests failed");
const proc = Bun.spawn(["ls", "-la"]);
await proc.exited;
const proc2 = Bun.spawn(["echo", "Hello"], { stdout: "pipe" });
const output = await new Response(proc2.stdout).text();
Testing (bun:test)
import { describe, test, expect, beforeEach, afterEach } from "bun:test";
describe("feature", () => {
let db: Database;
beforeEach(() => {
db = new Database(":memory:");
});
afterEach(() => {
db.close();
});
test("behavior", () => {
expect(result).toBe(expected);
expect(arr).toContain(item);
expect(fn).toThrow();
expect(obj).toEqual({ foo: "bar" });
});
test("async", async () => {
const result = await asyncFn();
expect(result).toBeDefined();
});
test.todo("pending feature");
test.skip("temporarily disabled");
});
bun test
bun test src/api.test.ts
bun test --watch
bun test --coverage
See testing.md for assertions, mocking, snapshots, best practices.
Environment Variables
console.log(Bun.env.NODE_ENV);
console.log(Bun.env.DATABASE_URL);
import { z } from "zod";
const EnvSchema = z.object({
NODE_ENV: z
.enum(["development", "production", "test"])
.default("development"),
DATABASE_URL: z.string(),
PORT: z.coerce.number().int().positive().default(3000),
API_KEY: z.string().min(32),
});
export const env = EnvSchema.parse(Bun.env);
Bun auto-loads .env, .env.local, .env.production.
Performance Utilities
const start = Bun.nanoseconds();
await doWork();
console.log(`Took ${(Bun.nanoseconds() - start) / 1_000_000}ms`);
const hash = Bun.hash(data);
const crc32 = Bun.hash.crc32(data);
const sha256 = Bun.CryptoHasher.hash("sha256", data);
await Bun.sleep(1000);
const { rss, heapUsed } = process.memoryUsage();
console.log("RSS:", rss / 1024 / 1024, "MB");
Building & Bundling
bun build ./index.ts --outfile dist/bundle.js --minify --sourcemap
bun build ./index.ts --outfile dist/bundle.js --external hono --external zod
bun build ./index.ts --compile --outfile myapp
bun build ./index.ts --compile --target=bun-linux-x64 --outfile myapp-linux
bun build ./index.ts --compile --target=bun-darwin-arm64 --outfile myapp-macos
bun build ./index.ts --compile --target=bun-windows-x64 --outfile myapp.exe
ALWAYS:
- Use Bun APIs when available (faster, native)
- Prepared statements for database queries
- Transactions for multi-statement operations
- argon2id for password hashing
- Validate environment variables at startup
- Close database connections when done
NEVER:
- String interpolation in SQL (use parameters)
- Plaintext passwords
- Ignore async disposal cleanup
- Deprecated Node.js APIs when Bun native exists
PREFER:
- Bun.file over fs.readFile
- Bun.write over fs.writeFile
- bun:sqlite over external SQLite libraries
- Bun.password over bcrypt/argon2 packages
- $ shell template over child_process
Examples:
Level up: Load the bun-first skill for migration auditing, dependency elimination, and the decision framework for when to use Bun native APIs over npm packages.