# Bun Fieldguide

> 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.

- Skill: `outfitter-dev/bun-fieldguide` (Agent Skill, multi-file: 6 files)
- Install (CLI): `npx skillmds@latest add outfitter-dev/bun-fieldguide`
- Raw SKILL.md: https://api.skillmd.com/api/skills/outfitter-dev/bun-fieldguide/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: outfitter-dev (https://skillmd.com/u/outfitter-dev)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/outfitter-dev/bun-fieldguide

---


# 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**:

```bash
bun install          # Install deps
bun add zod          # Add package
bun remove zod       # Remove package
bun update           # Update all
```

**Script execution**:

```bash
bun run dev          # Run package.json script
bun run src/index.ts # Execute TypeScript directly
bun --watch index.ts # Watch mode
```

**Testing**:

```bash
bun test             # All tests
bun test src/        # Directory
bun test --watch     # Watch mode
bun test --coverage  # With coverage
```

**Building**:

```bash
bun build ./index.ts --outfile dist/bundle.js
bun build ./index.ts --compile --outfile myapp  # Standalone executable
```

</runtime_basics>

## File Operations

<file_operations>

```typescript
// Read file (lazy, efficient)
const file = Bun.file("./data.json");
if (!(await file.exists())) throw new Error("File not found");

// Read formats
const text = await file.text();
const json = await file.json();
const buffer = await file.arrayBuffer();
const stream = file.stream(); // Large files

// Metadata
console.log(file.size, file.type);

// Write
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)

<sqlite>

```typescript
import { Database } from "bun:sqlite";

const db = new Database("app.db", {
  create: true,
  readwrite: true,
  strict: true,
});

// Create tables
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
  )
`);

// Prepared statements (always use these)
const getUser = db.prepare("SELECT * FROM users WHERE id = ?");
const createUser = db.prepare(
  "INSERT INTO users (id, email, name) VALUES (?, ?, ?) RETURNING *"
);

// Execution
const user = getUser.get("user-123"); // Single row
const all = db.prepare("SELECT * FROM users").all(); // All rows
db.prepare("DELETE FROM users WHERE id = ?").run("id"); // No return

// Named parameters
const stmt = db.prepare("SELECT * FROM users WHERE email = $email");
stmt.get({ $email: "alice@example.com" });

// Transactions (atomic, auto-rollback on error)
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(); // When done
```

See [sqlite-patterns.md](references/sqlite-patterns.md) for migrations, pooling, repository pattern.

</sqlite>

## Password Hashing

<password>

```typescript
// Hash (argon2id recommended)
const hash = await Bun.password.hash("password123", {
  algorithm: "argon2id",
  memoryCost: 65536, // 64 MB
  timeCost: 3,
});

// Or bcrypt
const bcryptHash = await Bun.password.hash("password123", {
  algorithm: "bcrypt",
  cost: 12,
});

// Verify
const isValid = await Bun.password.verify("password123", hash);
if (!isValid) throw new Error("Invalid password");
```

**Auth flow example**:

```typescript
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);
});
```

</password>

## HTTP Server

<http_server>

```typescript
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):

```typescript
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](references/server-patterns.md) for routing, middleware, file serving, streaming.

</http_server>

## WebSocket

<websocket>

```typescript
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](references/server-patterns.md) for client tracking, rooms, reconnection.

</websocket>

## Shell Operations

<shell>

```typescript
import { $ } from "bun";

// Run commands
const result = await $`ls -la`;
console.log(result.text());

// Variables (auto-escaped)
const dir = "./src";
await $`find ${dir} -name "*.ts"`;

// Check exit code
const { exitCode } = await $`npm test`.nothrow();
if (exitCode !== 0) console.error("Tests failed");

// Spawn process
const proc = Bun.spawn(["ls", "-la"]);
await proc.exited;

// Capture output
const proc2 = Bun.spawn(["echo", "Hello"], { stdout: "pipe" });
const output = await new Response(proc2.stdout).text();
```

</shell>

## Testing (bun:test)

<testing>

```typescript
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");
});
```

```bash
bun test                    # All tests
bun test src/api.test.ts    # Specific file
bun test --watch            # Watch mode
bun test --coverage         # With coverage
```

See [testing.md](references/testing.md) for assertions, mocking, snapshots, best practices.

</testing>

## Environment Variables

<environment>

```typescript
// Access
console.log(Bun.env.NODE_ENV);
console.log(Bun.env.DATABASE_URL);

// Zod validation
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`.

</environment>

## Performance Utilities

<performance>

```typescript
// High-resolution timing
const start = Bun.nanoseconds();
await doWork();
console.log(`Took ${(Bun.nanoseconds() - start) / 1_000_000}ms`);

// Hashing
const hash = Bun.hash(data);
const crc32 = Bun.hash.crc32(data);
const sha256 = Bun.CryptoHasher.hash("sha256", data);

// Sleep
await Bun.sleep(1000);

// Memory
const { rss, heapUsed } = process.memoryUsage();
console.log("RSS:", rss / 1024 / 1024, "MB");
```

</performance>

## Building & Bundling

<building>

```bash
# Production bundle
bun build ./index.ts --outfile dist/bundle.js --minify --sourcemap

# External deps
bun build ./index.ts --outfile dist/bundle.js --external hono --external zod

# Standalone executable
bun build ./index.ts --compile --outfile myapp

# Cross-compile
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
```

</building>

<rules>

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

</rules>

<references>

- [sqlite-patterns.md](references/sqlite-patterns.md) — migrations, pooling, repository, FTS
- [server-patterns.md](references/server-patterns.md) — HTTP, WebSocket, streaming, compression
- [testing.md](references/testing.md) — assertions, mocking, snapshots, best practices

**Examples:**

- [database-crud.md](examples/database-crud.md) — SQLite CRUD patterns
- [file-uploads.md](examples/file-uploads.md) — streaming file handling

**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.

</references>

