Bun is an all-in-one JavaScript runtime, bundler, and package manager — Bun.serve({ fetch(req) { return new Response("Hello") } }) starts an HTTP server. Bun.file("path.json") reads files with .text(), .json(), .arrayBuffer(), or .stream(). Bun.$\ls -la`runs shell commands.import { Database } from “bun:sqlite”provides SQLite.bun:testexportsdescribe, it, expect, mock, spyOn. Bun.build({ entrypoints: [“src/index.ts”], outdir: “dist” })bundles TypeScript.Bun.password.hash(pwd)hashes passwords with bcrypt or argon2.Bun.CryptoHasherprovides SHA-256/SHA-512 hashing.Bun.servesupports HTTP/2, WebSocket withws.send()/ws.close(), and TLS. bun —hothot-reloads without restart. Bun is Node.js-compatible —npm installworks viabun install. Bun.sql` provides native PostgreSQL connection pooling. Claude Code generates Bun HTTP servers, SQLite databases, bundling configs, and test suites.
CLAUDE.md for Bun
## Bun Stack
- Version: bun >= 1.1
- Server: Bun.serve({ port: 3000, fetch: async (req) => new Response(body, { headers }) })
- File: const file = Bun.file("./data.json"); const data = await file.json()
- Write: await Bun.write("output.txt", content)
- Shell: const output = await Bun.$`cat package.json`.json()
- SQLite: import { Database } from "bun:sqlite"; const db = new Database("app.db")
- Test: import { describe, it, expect, mock } from "bun:test"
- Build: Bun.build({ entrypoints: ["./src/index.ts"], outdir: "./dist", target: "bun" })
- Hash: const hash = Bun.hash("string") — FNV hash; for cryptographic: new Bun.CryptoHasher("sha256")
HTTP Server
// src/server.ts — Bun HTTP server with routing
import { type Server } from "bun"
import { handleAuth } from "./routes/auth"
import { handlePosts } from "./routes/posts"
import { handleStatic } from "./routes/static"
type Handler = (req: Request, params: Record<string, string>) => Promise<Response> | Response
// Simple router
const routes: Array<{
method: string
pattern: URLPattern
handler: Handler
}> = [
{ method: "GET", pattern: new URLPattern({ pathname: "/health" }), handler: handleHealth },
{ method: "GET", pattern: new URLPattern({ pathname: "/api/posts" }), handler: handlePosts.list },
{ method: "POST", pattern: new URLPattern({ pathname: "/api/posts" }), handler: handlePosts.create },
{ method: "GET", pattern: new URLPattern({ pathname: "/api/posts/:id" }), handler: handlePosts.get },
{ method: "DELETE", pattern: new URLPattern({ pathname: "/api/posts/:id" }), handler: handlePosts.delete },
{ method: "POST", pattern: new URLPattern({ pathname: "/api/auth/login" }), handler: handleAuth.login },
{ method: "POST", pattern: new URLPattern({ pathname: "/api/auth/register" }), handler: handleAuth.register },
]
function matchRoute(req: Request): { handler: Handler; params: Record<string, string> } | null {
const url = new URL(req.url)
for (const route of routes) {
if (route.method !== req.method) continue
const match = route.pattern.exec(url)
if (match) {
return {
handler: route.handler,
params: (match.pathname.groups ?? {}) as Record<string, string>,
}
}
}
return null
}
function handleHealth(_req: Request): Response {
return Response.json({
ok: true,
timestamp: new Date().toISOString(),
memory: process.memoryUsage.rss(),
})
}
const server: Server = Bun.serve({
port: parseInt(process.env.PORT ?? "3000"),
hostname: "0.0.0.0",
async fetch(req) {
const match = matchRoute(req)
if (!match) {
return Response.json({ message: "Not found" }, { status: 404 })
}
try {
return await match.handler(req, match.params)
} catch (err) {
console.error("[Server error]", err)
return Response.json({ message: "Internal server error" }, { status: 500 })
}
},
// WebSocket support
websocket: {
open(ws) {
console.log("[WS] connected", ws.remoteAddress)
},
message(ws, message) {
ws.send(`echo: ${message}`)
},
close(ws) {
console.log("[WS] disconnected")
},
},
// TLS for production
...(process.env.NODE_ENV === "production" && process.env.TLS_KEY ? {
tls: {
key: Bun.file(process.env.TLS_KEY),
cert: Bun.file(process.env.TLS_CERT!),
},
} : {}),
})
console.log(`🚀 Server running at ${server.url}`)
SQLite Database
// src/db/sqlite.ts — Bun SQLite with query helpers
import { Database } from "bun:sqlite"
import { existsSync } from "fs"
const DB_PATH = process.env.DATABASE_PATH ?? "./app.db"
const db = new Database(DB_PATH, { create: true })
// Enable WAL mode for concurrent reads
db.run("PRAGMA journal_mode = WAL")
db.run("PRAGMA synchronous = NORMAL")
db.run("PRAGMA cache_size = 10000")
db.run("PRAGMA foreign_keys = ON")
// ── Schema ─────────────────────────────────────────────────────────────────
function migrate() {
db.transaction(() => {
db.run(`
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
email TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
hashed_password TEXT,
role TEXT NOT NULL DEFAULT 'user',
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
)
`)
db.run(`
CREATE TABLE IF NOT EXISTS posts (
id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
title TEXT NOT NULL,
slug TEXT NOT NULL UNIQUE,
content TEXT NOT NULL,
excerpt TEXT,
published INTEGER NOT NULL DEFAULT 0,
author_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
)
`)
db.run("CREATE INDEX IF NOT EXISTS idx_posts_slug ON posts(slug)")
db.run("CREATE INDEX IF NOT EXISTS idx_posts_author ON posts(author_id)")
})()
}
migrate()
// ── Type helpers ───────────────────────────────────────────────────────────
type Post = {
id: string
title: string
slug: string
content: string
excerpt: string | null
published: number
author_id: string
created_at: string
}
// Prepared statements for hot paths
const getPostBySlug = db.prepare<Post, [string]>(
"SELECT * FROM posts WHERE slug = ? AND published = 1",
)
const getPostById = db.prepare<Post, [string]>(
"SELECT * FROM posts WHERE id = ?",
)
const listPosts = db.prepare<Post, [number, number]>(
"SELECT * FROM posts WHERE published = 1 ORDER BY created_at DESC LIMIT ? OFFSET ?",
)
const insertPost = db.prepare<Post, [string, string, string, string | null, number, string]>(
"INSERT INTO posts (title, slug, content, excerpt, published, author_id) VALUES (?, ?, ?, ?, ?, ?) RETURNING *",
)
export const postdb = {
findBySlug: (slug: string) => getPostBySlug.get(slug),
findById: (id: string) => getPostById.get(id),
list: (limit = 10, offset = 0): Post[] => listPosts.all(limit, offset),
create: (data: { title: string; slug: string; content: string; excerpt?: string; published: boolean; authorId: string }) => {
return insertPost.get(data.title, data.slug, data.content, data.excerpt ?? null, data.published ? 1 : 0, data.authorId)
},
}
export { db }
Bun Test Suite
// src/routes/posts.test.ts — Bun native test runner
import { describe, it, expect, beforeAll, afterAll, mock } from "bun:test"
import { Database } from "bun:sqlite"
import { handlePosts } from "./posts"
// In-memory test DB
const testDb = new Database(":memory:")
describe("Posts API", () => {
beforeAll(() => {
testDb.run(`CREATE TABLE posts (
id TEXT PRIMARY KEY DEFAULT (lower(hex(randomblob(16)))),
title TEXT NOT NULL,
slug TEXT NOT NULL,
content TEXT NOT NULL,
excerpt TEXT,
published INTEGER DEFAULT 0,
author_id TEXT NOT NULL,
created_at TEXT DEFAULT (datetime('now'))
)`)
})
it("GET /api/posts returns empty list", async () => {
const req = new Request("http://localhost/api/posts")
const res = await handlePosts.list(req, {})
expect(res.status).toBe(200)
const body = await res.json() as { posts: unknown[] }
expect(Array.isArray(body.posts)).toBe(true)
})
it("POST /api/posts creates a post", async () => {
const req = new Request("http://localhost/api/posts", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
title: "Test Post Title",
content: "x".repeat(20),
published: true,
}),
})
const res = await handlePosts.create(req, {})
expect(res.status).toBe(201)
})
it("computes slug from title", () => {
const slug = "Hello World! Special Chars #123"
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-|-$/g, "")
expect(slug).toBe("hello-world-special-chars-123")
})
})
Shell Scripting
// scripts/deploy.ts — Bun shell scripting
import { $ } from "bun"
const VERSION = process.env.VERSION ?? "latest"
const REGISTRY = process.env.REGISTRY ?? "registry.example.com"
async function deploy() {
console.log("🔍 Running tests...")
await $`bun test`.text()
console.log("🏗️ Building...")
const buildResult = await Bun.build({
entrypoints: ["./src/index.ts"],
outdir: "./dist",
target: "bun",
minify: true,
})
if (!buildResult.success) {
for (const log of buildResult.logs) console.error(log)
process.exit(1)
}
console.log(`📦 Building Docker image ${REGISTRY}/app:${VERSION}...`)
await $`docker build -t ${REGISTRY}/app:${VERSION} .`
console.log("🚀 Pushing...")
await $`docker push ${REGISTRY}/app:${VERSION}`
const deployTime = new Date().toISOString()
await Bun.write("./dist/deploy.json", JSON.stringify({ version: VERSION, deployedAt: deployTime }))
console.log(`✅ Deployed ${VERSION} at ${deployTime}`)
}
await deploy()
For the Node.js alternative when maximum ecosystem compatibility (native addons, mature npm packages with native bindings), larger deployment platform support, and an established production track record are paramount — Node.js has broader support for native modules while Bun is incompatible with some native addons but runs pure JS/TS packages well, see the Node.js guide. For the Deno alternative when built-in TypeScript, a permissions model for security sandboxing, native Web APIs (fetch, Temporal, etc.) without polyfills, and first-class JSR package registry support are preferred — Deno and Bun both target modern JS runtimes but Deno prioritizes security and standards while Bun prioritizes performance and Node.js compatibility, see the Deno guide. The Claude Skills 360 bundle includes Bun skill sets covering HTTP servers, SQLite, bundling, and testing. Start with the free tier to try Bun runtime development generation.