Claude Code for Bun: HTTP Server, File I/O, and SQLite — Claude Skills 360 Blog
Blog / Backend / Claude Code for Bun: HTTP Server, File I/O, and SQLite
Backend

Claude Code for Bun: HTTP Server, File I/O, and SQLite

Published: February 2, 2027
Read time: 8 min read
By: Claude Skills 360

Bun is a JavaScript runtime with a built-in HTTP server, SQLite driver, test runner, bundler, and package manager. Bun.serve() handles HTTP requests and WebSocket upgrades in a single call, running on Bun’s native Zig/JavaScriptCore implementation — significantly faster than Node.js on I/O-bound workloads. Bun.file() wraps file handles as BunFile objects with lazy streaming. bun:sqlite is a zero-dependency SQLite driver bound directly to Bun’s native layer — no npm package required. Bun’s test runner uses Jest-compatible expect matchers and runs TypeScript tests without compilation. Bun.Worker runs scripts in background threads with postMessage. Claude Code generates Bun HTTP servers, SQLite data layers, WebSocket handlers, test suites, and the package.json scripts for production Bun applications.

CLAUDE.md for Bun Projects

## Bun Stack
- Version: bun >= 1.1
- HTTP: Bun.serve({ fetch(req) { ... } }) — handles HTTP + WebSocket upgrades
- SQLite: import { Database } from "bun:sqlite" — built-in, no npm package
- Files: Bun.file(path) → BunFile — lazy, streaming, Response-compatible
- Test: bun test — Jest-compatible, no config needed, TypeScript native
- Workers: new Worker("./worker.ts") — Bun.Worker for CPU-bound tasks
- Dev: bun --hot server.ts — HMR without full restarts
- Build: Bun.build({ entrypoints, outdir }) — bundler API

HTTP Server

// server.ts — Bun HTTP server with routing
import { Database } from "bun:sqlite"
import { join } from "path"

const db = new Database("orders.db")
db.run(`
  CREATE TABLE IF NOT EXISTS orders (
    id TEXT PRIMARY KEY,
    customer_id TEXT NOT NULL,
    status TEXT NOT NULL DEFAULT 'pending',
    total_cents INTEGER NOT NULL,
    items TEXT NOT NULL,
    created_at TEXT NOT NULL DEFAULT (datetime('now'))
  )
`)

const server = Bun.serve({
  port: process.env.PORT ? parseInt(process.env.PORT) : 3000,

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

    // Static file serving
    if (url.pathname.startsWith("/static/")) {
      const filePath = join("public", url.pathname.slice(8))
      const file = Bun.file(filePath)
      if (await file.exists()) {
        return new Response(file)  // Bun streams file lazily
      }
      return new Response("Not found", { status: 404 })
    }

    // API routes
    if (url.pathname === "/api/orders" && method === "GET") {
      return handleListOrders(req, url)
    }
    if (url.pathname === "/api/orders" && method === "POST") {
      return handleCreateOrder(req)
    }

    const orderMatch = url.pathname.match(/^\/api\/orders\/([^/]+)$/)
    if (orderMatch) {
      const orderId = orderMatch[1]
      if (method === "GET") return handleGetOrder(orderId)
      if (method === "PATCH") return handleUpdateOrder(orderId, req)
      if (method === "DELETE") return handleDeleteOrder(orderId)
    }

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

  // WebSocket handler — upgraded from HTTP
  websocket: {
    message(ws, message) {
      ws.send(`Echo: ${message}`)
    },
    open(ws) {
      console.log("WebSocket connected")
    },
    close(ws) {
      console.log("WebSocket disconnected")
    },
  },

  error(error) {
    console.error(error)
    return new Response("Internal server error", { status: 500 })
  },
})

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

SQLite Data Layer

// lib/db.ts — bun:sqlite data access
import { Database, Statement } from "bun:sqlite"
import { randomUUID } from "crypto"

const db = new Database("orders.db", { create: true })

// Enable WAL mode for better concurrent read performance
db.run("PRAGMA journal_mode = WAL")
db.run("PRAGMA foreign_keys = ON")

// Prepared statements — cached, type-safe
const statements = {
  insertOrder: db.prepare(`
    INSERT INTO orders (id, customer_id, status, total_cents, items)
    VALUES ($id, $customerId, 'pending', $totalCents, $items)
  `),

  getOrder: db.prepare(`
    SELECT * FROM orders WHERE id = $id
  `),

  listOrders: db.prepare(`
    SELECT * FROM orders
    WHERE customer_id = $customerId
    ORDER BY created_at DESC
    LIMIT $limit OFFSET $offset
  `),

  updateStatus: db.prepare(`
    UPDATE orders SET status = $status WHERE id = $id
  `),

  deleteOrder: db.prepare(`
    DELETE FROM orders WHERE id = $id
  `),
}

interface CreateOrderInput {
  customerId: string
  items: OrderItem[]
  totalCents: number
}

interface OrderRow {
  id: string
  customer_id: string
  status: string
  total_cents: number
  items: string
  created_at: string
}

function parseOrder(row: OrderRow): Order {
  return {
    id: row.id,
    customerId: row.customer_id,
    status: row.status as Order["status"],
    totalCents: row.total_cents,
    items: JSON.parse(row.items),
    createdAt: row.created_at,
  }
}

export function createOrder(input: CreateOrderInput): Order {
  const id = randomUUID()

  // Transactions for atomicity
  const insert = db.transaction(() => {
    statements.insertOrder.run({
      $id: id,
      $customerId: input.customerId,
      $totalCents: input.totalCents,
      $items: JSON.stringify(input.items),
    })
    return statements.getOrder.get({ $id: id }) as OrderRow
  })

  return parseOrder(insert())
}

export function getOrder(orderId: string): Order | null {
  const row = statements.getOrder.get({ $id: orderId }) as OrderRow | null
  return row ? parseOrder(row) : null
}

export function listOrders(
  customerId: string,
  limit = 20,
  offset = 0
): Order[] {
  const rows = statements.listOrders.all({
    $customerId: customerId,
    $limit: limit,
    $offset: offset,
  }) as OrderRow[]
  return rows.map(parseOrder)
}

Request Handlers

// lib/handlers.ts — HTTP request handlers
import { createOrder, getOrder, listOrders, updateOrderStatus } from "./db"

export async function handleListOrders(req: Request, url: URL): Promise<Response> {
  const customerId = url.searchParams.get("customer_id")
  if (!customerId) {
    return Response.json({ error: "Missing customer_id" }, { status: 400 })
  }

  const limit = parseInt(url.searchParams.get("limit") ?? "20")
  const offset = parseInt(url.searchParams.get("offset") ?? "0")

  const orders = listOrders(customerId, limit, offset)
  return Response.json({ orders, limit, offset })
}

export async function handleCreateOrder(req: Request): Promise<Response> {
  let body: unknown
  try {
    body = await req.json()
  } catch {
    return Response.json({ error: "Invalid JSON" }, { status: 400 })
  }

  // Simple validation
  const { customerId, items } = body as any
  if (!customerId || !Array.isArray(items) || items.length === 0) {
    return Response.json({ error: "customerId and items required" }, { status: 422 })
  }

  const totalCents = items.reduce(
    (sum: number, item: any) => sum + item.priceCents * item.quantity,
    0
  )

  const order = createOrder({ customerId, items, totalCents })
  return Response.json(order, { status: 201 })
}

export async function handleGetOrder(orderId: string): Promise<Response> {
  const order = getOrder(orderId)
  if (!order) return Response.json({ error: "Not found" }, { status: 404 })
  return Response.json(order)
}

Test Suite

// tests/orders.test.ts — bun:test native test runner
import { describe, test, expect, beforeEach, afterAll } from "bun:test"
import { Database } from "bun:sqlite"

// Use in-memory SQLite for tests
const testDb = new Database(":memory:")

describe("Order creation", () => {
  beforeEach(() => {
    testDb.run("DELETE FROM orders")
  })

  test("creates order with correct total", () => {
    const order = createOrderWithDb(testDb, {
      customerId: "cust-123",
      items: [
        { productId: "p1", quantity: 2, priceCents: 999 },
        { productId: "p2", quantity: 1, priceCents: 1499 },
      ],
      totalCents: 3497,
    })

    expect(order.customerId).toBe("cust-123")
    expect(order.totalCents).toBe(3497)
    expect(order.status).toBe("pending")
    expect(order.items).toHaveLength(2)
  })

  test("retrieves created order", () => {
    const created = createOrderWithDb(testDb, {
      customerId: "cust-456",
      items: [{ productId: "p1", quantity: 1, priceCents: 500 }],
      totalCents: 500,
    })

    const retrieved = getOrderWithDb(testDb, created.id)
    expect(retrieved).not.toBeNull()
    expect(retrieved!.id).toBe(created.id)
  })

  test("HTTP server returns 400 for missing customer_id", async () => {
    const response = await fetch("http://localhost:3000/api/orders")
    expect(response.status).toBe(400)
    const body = await response.json()
    expect(body.error).toInclude("customer_id")
  })
})

afterAll(() => {
  testDb.close()
})

Worker Thread for CPU Work

// workers/report-generator.ts — Bun.Worker for CPU-bound tasks
// This runs in a separate thread

self.onmessage = async (event: MessageEvent) => {
  const { orderId, type } = event.data

  if (type === "generate-report") {
    // CPU-intensive work without blocking main thread
    const data = await fetchOrderData(orderId)
    const pdf = await generatePdf(data)

    self.postMessage({ type: "report-ready", pdf: pdf.buffer }, [pdf.buffer])
  }
}
// main thread usage
const worker = new Worker("./workers/report-generator.ts")

worker.onmessage = (event) => {
  if (event.data.type === "report-ready") {
    const pdfBuffer = event.data.pdf
    // Send to client
  }
}

worker.postMessage({ orderId: "ord-123", type: "generate-report" })

For the Node.js Fastify or Hono framework that provides similar routing ergonomics on Node.js with a larger ecosystem of middleware, the Express and Fastify guide covers production Node.js server patterns. For the Deno HTTP server with built-in KV and the same TypeScript-native runtime philosophy as Bun with different runtime APIs, see the Deno KV guide for the Deno runtime stack. The Claude Skills 360 bundle includes Bun skill sets covering HTTP server setup, SQLite integration, and the test runner. Start with the free tier to try Bun server generation.

Keep Reading

Backend

Claude Code for Bun: Fast JavaScript Runtime and Toolkit

Build with Bun and Claude Code — Bun.serve for HTTP servers, Bun.file for fast file I/O, Bun.$ for shell commands, Bun.sql for SQLite and PostgreSQL, Bun.build for bundling, bun:test for testing, Bun.hash for hashing, bun.lock for deterministic installs, bun run for package.json scripts, hot reloading with --hot, bun init for project scaffolding, and compatibility with Node.js modules.

6 min read Jun 13, 2027
Backend

Claude Code for Express.js Advanced: Patterns for Production APIs

Advanced Express.js patterns with Claude Code — typed request handlers with RequestHandler generics, async error handling middleware, Zod validation middleware factory, rate limiting with express-rate-limit and Redis store, helmet security middleware, compression, dependency injection with tsyringe, file upload with multer and S3, pagination utilities, JWT middleware, and structured logging with pino.

6 min read Jun 8, 2027
Backend

Claude Code for KeystoneJS: Node.js CMS and App Framework

Build full-stack apps with KeystoneJS and Claude Code — config with lists, fields.text and fields.relationship for schema definition, access control with isAuthenticated and isAdmin functions, hooks with beforeOperation and afterOperation, GraphQL API auto-generation from schema, AdminUI for content management, session with statelessSessions, Prisma adapter for database, file storage with images and files fields, and custom REST endpoints.

6 min read Jun 7, 2027

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free