| name | bun-runtime-expert |
| description | 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.
|
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
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
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
Bun.serve({
fetch(req, server) {
if (server.upgrade(req)) return
return new Response("HTTP response")
},
websocket: {
open(ws) {
ws.subscribe("chat")
ws.send(JSON.stringify({ type: "connected" }))
},
message(ws, message) {
ws.publish("chat", message)
},
close(ws) {
ws.unsubscribe("chat")
},
},
})
Database — Bun.sql
PostgreSQL (Built-in, no driver needed)
import { sql } from "bun"
const users = await sql`SELECT * FROM users WHERE active = ${true}`
type User = { id: string; email: string; name: string }
const user = await sql<User[]>`
SELECT id, email, name
FROM users
WHERE email = ${email}
LIMIT 1
`
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
})
const db = new Bun.SQL({
url: process.env.DATABASE_URL,
max: 20,
idleTimeout: 30,
})
SQLite (Zero-config, built-in)
import { Database } from "bun:sqlite"
const db = new Database("mydb.sqlite")
const getUser = db.prepare<{ id: number; name: string }, [string]>(
"SELECT id, name FROM users WHERE email = ?"
)
const user = getUser.get("alice@example.com")
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
const s3 = new Bun.S3Client({
bucket: "my-bucket",
region: "us-east-1",
endpoint: "https://xxx.r2.cloudflarestorage.com",
})
await s3.write("uploads/avatar.png", imageBuffer, {
type: "image/png",
acl: "public-read",
})
const file = s3.file("uploads/avatar.png")
const buffer = await file.arrayBuffer()
const url = await s3.presign("uploads/avatar.png", {
expiresIn: 3600,
method: "GET",
})
await s3.delete("uploads/old-avatar.png")
const objects = await s3.list({ prefix: "uploads/" })
Testing — bun:test
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
expect(mockFetch).toHaveBeenCalledTimes(1)
})
})
it("renders correctly", () => {
const output = renderComponent()
expect(output).toMatchSnapshot()
})
bun test
bun test --watch
bun test --coverage
bun test --test-name-pattern "Calculator"
bun test math.test.ts
Bundler
await Bun.build({
entrypoints: ["./src/index.ts"],
outdir: "./dist",
target: "browser",
format: "esm",
splitting: true,
minify: true,
sourcemap: "external",
define: {
"process.env.NODE_ENV": JSON.stringify("production"),
},
plugins: [
],
})
bun build ./src/index.ts --outdir ./dist --minify
bun build ./src/index.ts --outfile ./dist/bundle.js
Package Manager
bun install
bun add express
bun add -d @types/bun
bun remove express
bun update
bun run dev
bun run build
bun index.ts
bun --watch index.ts
bun install
bunfig.toml Configuration
[install]
registry = "https://registry.npmjs.org"
frozen = true
[run]
bun = true
Bun Shell ($)
import { $ } from "bun"
const result = await $`ls -la`.text()
const count = await $`cat package.json | grep name`.text()
const filename = "my file.txt"
await $`rm ${filename}`
const { stdout, stderr, exitCode } = await $`git status`.quiet()
await $`echo "hello" > output.txt`
await $`
mkdir -p dist
bun build ./src/index.ts --outdir dist
echo "Build complete"
`
Key Rules
- Use tagged template literals for SQL — never string concatenation (injection safe)
bun:test not Jest — same API, 10x faster
Bun.file() for file reads — lazy, streaming, faster than fs.readFile
Response.json() not new Response(JSON.stringify()) — cleaner API
Bun.serve error handler — always define error() for uncaught handler errors
- Pool connections — set
max on Bun.SQL for production
bun --watch for development — built-in hot reload
- TypeScript natively — no build step needed in development
bun build for production — minify + tree-shake before deploying
bunx instead of npx — runs package CLIs faster