# Bun Runtime Expert

> Expert skill for the Bun JavaScript/TypeScript runtime. Use whenever the user is building, running, or debugging code with Bun — covering Bun.serve (HTTP server), Bun.sql (PostgreSQL), Bun.s3 (object storage), bun:test (testing), the Bun bundler, and package manager. Trigger on any mention of Bun, bun install, bun run, Bun.serve, Bun.file, Bun.sql, bun:test, or when the user wants a fast Node.js alternative. Also trigger for Bun shell scripting or Bun-specific APIs.

- Skill: `roedyrustam/bun-runtime-expert-2` (Agent Skill)
- Install (CLI): `npx skillmds@latest add roedyrustam/bun-runtime-expert-2`
- Raw SKILL.md: https://api.skillmd.com/api/skills/roedyrustam/bun-runtime-expert-2/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Data & Analytics
- Author: roedyrustam (https://skillmd.com/u/roedyrustam)
- Updated: 2026-09-21
- Page: https://skillmd.com/skills/roedyrustam/bun-runtime-expert-2

---


# Bun Runtime Expert

Fast, all-in-one JavaScript/TypeScript runtime — server, bundler, package manager, test runner.

---

## Why Bun

| Feature | Bun | Node.js |
|---------|-----|---------|
| Startup time | ~5ms | ~50ms |
| `bun install` | ~100ms | npm: ~3s |
| Built-in SQLite | ✅ | ❌ |
| Built-in test runner | ✅ | ❌ (need Jest) |
| Built-in bundler | ✅ | ❌ (need webpack) |
| TypeScript natively | ✅ | ❌ (need ts-node) |
| Web APIs (fetch, Request) | ✅ native | Partial |

---

## HTTP Server — `Bun.serve`

### Basic Server
```typescript
const server = Bun.serve({
  port: 3000,
  hostname: "0.0.0.0",

  async fetch(req: Request): Promise<Response> {
    const url = new URL(req.url)

    if (url.pathname === "/health") {
      return Response.json({ status: "ok" })
    }

    if (url.pathname === "/api/users" && req.method === "GET") {
      const users = await getUsers()
      return Response.json(users)
    }

    if (url.pathname === "/api/users" && req.method === "POST") {
      const body = await req.json()
      const user = await createUser(body)
      return Response.json(user, { status: 201 })
    }

    return new Response("Not Found", { status: 404 })
  },

  error(err: Error): Response {
    console.error(err)
    return Response.json({ error: "Internal Server Error" }, { status: 500 })
  },
})

console.log(`Server running at http://localhost:${server.port}`)
```

### Router Pattern
```typescript
type Handler = (req: Request, params: Record<string, string>) => Promise<Response>

class Router {
  private routes = new Map<string, Handler>()

  add(method: string, path: string, handler: Handler) {
    this.routes.set(`${method}:${path}`, handler)
    return this
  }

  get(path: string, handler: Handler) { return this.add("GET", path, handler) }
  post(path: string, handler: Handler) { return this.add("POST", path, handler) }
  put(path: string, handler: Handler) { return this.add("PUT", path, handler) }
  delete(path: string, handler: Handler) { return this.add("DELETE", path, handler) }

  async handle(req: Request): Promise<Response> {
    const url = new URL(req.url)
    const key = `${req.method}:${url.pathname}`
    const handler = this.routes.get(key)

    if (!handler) return new Response("Not Found", { status: 404 })
    return handler(req, {})
  }
}

const router = new Router()
  .get("/api/posts", listPosts)
  .post("/api/posts", createPost)
  .get("/api/posts/:id", getPost)

Bun.serve({ fetch: req => router.handle(req) })
```

### WebSocket Support
```typescript
Bun.serve({
  fetch(req, server) {
    if (server.upgrade(req)) return // Upgraded to WS
    return new Response("HTTP response")
  },
  websocket: {
    open(ws) {
      ws.subscribe("chat")
      ws.send(JSON.stringify({ type: "connected" }))
    },
    message(ws, message) {
      // Broadcast to all subscribers
      ws.publish("chat", message)
    },
    close(ws) {
      ws.unsubscribe("chat")
    },
  },
})
```

---

## Database — `Bun.sql`

### PostgreSQL (Built-in, no driver needed)
```typescript
import { sql } from "bun"

// Bun.sql uses tagged template literals — SQL injection safe
const users = await sql`SELECT * FROM users WHERE active = ${true}`

// With types
type User = { id: string; email: string; name: string }
const user = await sql<User[]>`
  SELECT id, email, name
  FROM users
  WHERE email = ${email}
  LIMIT 1
`

// Transactions
await sql.begin(async (tx) => {
  const [newUser] = await tx<User[]>`
    INSERT INTO users (email, name) VALUES (${email}, ${name})
    RETURNING *
  `
  await tx`
    INSERT INTO audit_log (user_id, action) VALUES (${newUser.id}, 'signup')
  `
  return newUser
})

// Configure connection
const db = new Bun.SQL({
  url: process.env.DATABASE_URL,
  max: 20,           // pool size
  idleTimeout: 30,   // seconds
})
```

### SQLite (Zero-config, built-in)
```typescript
import { Database } from "bun:sqlite"

const db = new Database("mydb.sqlite")

// Prepare statements (reuse for performance)
const getUser = db.prepare<{ id: number; name: string }, [string]>(
  "SELECT id, name FROM users WHERE email = ?"
)

const user = getUser.get("alice@example.com")

// Transactions
const insertUser = db.prepare("INSERT INTO users (email, name) VALUES (?, ?)")
const insertLog = db.prepare("INSERT INTO logs (user_id) VALUES (?)")

const signup = db.transaction((email: string, name: string) => {
  const info = insertUser.run(email, name)
  insertLog.run(info.lastInsertRowid)
  return info.lastInsertRowid
})

const id = signup("bob@example.com", "Bob")
```

---

## Object Storage — `Bun.s3`

```typescript
// Configure (auto-reads AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)
const s3 = new Bun.S3Client({
  bucket: "my-bucket",
  region: "us-east-1",
  // Or use custom endpoint for R2/MinIO:
  endpoint: "https://xxx.r2.cloudflarestorage.com",
})

// Upload
await s3.write("uploads/avatar.png", imageBuffer, {
  type: "image/png",
  acl: "public-read",
})

// Download
const file = s3.file("uploads/avatar.png")
const buffer = await file.arrayBuffer()

// Presigned URL
const url = await s3.presign("uploads/avatar.png", {
  expiresIn: 3600, // 1 hour
  method: "GET",
})

// Delete
await s3.delete("uploads/old-avatar.png")

// List
const objects = await s3.list({ prefix: "uploads/" })
```

---

## Testing — `bun:test`

```typescript
// math.test.ts
import { describe, it, expect, beforeEach, afterEach, mock, spyOn } from "bun:test"

describe("Calculator", () => {
  it("adds two numbers", () => {
    expect(1 + 2).toBe(3)
  })

  it("handles async operations", async () => {
    const result = await fetchData("https://api.example.com")
    expect(result).toMatchObject({ status: "ok" })
  })

  it("mocks functions", () => {
    const mockFetch = mock(() => Promise.resolve({ ok: true }))
    globalThis.fetch = mockFetch as any

    // test code that calls fetch...
    expect(mockFetch).toHaveBeenCalledTimes(1)
  })
})

// Snapshot testing
it("renders correctly", () => {
  const output = renderComponent()
  expect(output).toMatchSnapshot()
})
```

```bash
# Run tests
bun test

# Watch mode
bun test --watch

# Coverage
bun test --coverage

# Filter by name
bun test --test-name-pattern "Calculator"

# Specific file
bun test math.test.ts
```

---

## Bundler

```typescript
// build.ts
await Bun.build({
  entrypoints: ["./src/index.ts"],
  outdir: "./dist",
  target: "browser",    // or "node" or "bun"
  format: "esm",        // or "cjs" or "iife"
  splitting: true,      // code splitting
  minify: true,
  sourcemap: "external",
  define: {
    "process.env.NODE_ENV": JSON.stringify("production"),
  },
  plugins: [
    // Custom plugins (compatible with esbuild API)
  ],
})
```

```bash
# Build from CLI
bun build ./src/index.ts --outdir ./dist --minify

# Bundle to single file
bun build ./src/index.ts --outfile ./dist/bundle.js
```

---

## Package Manager

```bash
# Install (fastest package manager)
bun install                    # install from package.json
bun add express                # add dependency
bun add -d @types/bun          # add devDependency
bun remove express             # remove
bun update                     # update all

# Run scripts
bun run dev
bun run build

# Execute files
bun index.ts                   # run TypeScript directly
bun --watch index.ts           # hot reload

# Workspaces
bun install                    # installs all workspace packages
```

### `bunfig.toml` Configuration
```toml
[install]
registry = "https://registry.npmjs.org"
frozen = true  # like --frozen-lockfile

[run]
bun = true  # prefer bun over node for scripts
```

---

## Bun Shell (`$`)

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

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

// Pipe
const count = await $`cat package.json | grep name`.text()

// Template with variables (auto-escaped)
const filename = "my file.txt"
await $`rm ${filename}`         // safe: rm "my file.txt"

// Capture output
const { stdout, stderr, exitCode } = await $`git status`.quiet()

// Write file
await $`echo "hello" > output.txt`

// Script mode
await $`
  mkdir -p dist
  bun build ./src/index.ts --outdir dist
  echo "Build complete"
`
```

---

## Key Rules

1. **Use tagged template literals for SQL** — never string concatenation (injection safe)
2. **`bun:test` not Jest** — same API, 10x faster
3. **`Bun.file()` for file reads** — lazy, streaming, faster than `fs.readFile`
4. **`Response.json()` not `new Response(JSON.stringify())`** — cleaner API
5. **`Bun.serve` error handler** — always define `error()` for uncaught handler errors
6. **Pool connections** — set `max` on `Bun.SQL` for production
7. **`bun --watch`** for development — built-in hot reload
8. **TypeScript natively** — no build step needed in development
9. **`bun build` for production** — minify + tree-shake before deploying
10. **`bunx` instead of `npx`** — runs package CLIs faster

