Bun HTTP Server mit Claude Code 2026

Bun ist der schnellste JavaScript-Runtime 2026 — 3x schneller als Node.js. Claude Code kennt Bun.serve(), WebSockets, die native SQLite-Integration und Performance-Optimierungen für produktionsreife Backend-Services.

Warum Bun 2026 der bevorzugte Runtime für KI-Backends ist

Als Claude Code im Jahr 2025 erstmals Bun als Standard-Runtime für neue Backend-Projekte empfahl, war die Community skeptisch. Heute, Mitte 2026, ist der Schwenk vollzogen: Bun überholt Node.js nicht nur in Benchmarks, sondern auch in der täglichen Praxis von Entwicklungsteams, die KI-gestützte Services bauen.

Der Grund liegt in der Architektur. Bun basiert auf JavaScriptCore (JSC) — der Engine hinter Safari — statt auf V8. JSC startet schneller, verbraucht weniger RAM beim Kaltstart und hat einen aggressiveren JIT-Kompiler für kurzlebige Prozesse. Genau das, was API-Server und Webhook-Handler brauchen.

Performance Warum JavaScriptCore schneller startet

V8 (Node.js/Deno) optimiert für Langläufer-Prozesse mit warmem Cache. JSC (Bun) priorisiert schnellen Kaltstart und niedrige Latenz bei kleinen Payloads — ideal für Serverless und containerisierte Microservices. Ein Bun-Prozess ist in unter 10ms startbereit; Node.js braucht 80–120ms.

Claude Code erkennt Bun-Projekte anhand von bun.lockb oder bunfig.toml und schaltet automatisch auf Bun-spezifische API-Vorschläge um. Das bedeutet: Wer mit Claude Code Backend-Services entwickelt, profitiert sofort aus den nativen Bun-APIs ohne Wrapper-Libraries.

1. Bun.serve() — HTTP-Server in einer Funktion

Bun bringt einen eingebauten HTTP-Server mit, der ohne externe Dependencies auskommt. Bun.serve() ist der Einstiegspunkt — kein Express, kein Fastify, kein Koa notwendig.

Bun.serve Minimaler HTTP-Server

// server.ts — läuft mit: bun run server.ts const server = Bun.serve({ port: 3000, hostname: "0.0.0.0", async fetch(req: Request): Promise<Response> { const url = new URL(req.url); // Einfache Route: GET / if (url.pathname === "/" && req.method === "GET") { return new Response("Hallo von Bun!", { headers: { "Content-Type": "text/plain; charset=utf-8" }, }); } // JSON API Endpunkt if (url.pathname === "/api/status") { return Response.json({ status: "ok", runtime: "bun", version: Bun.version, uptime: process.uptime(), }); } // 404 Fallback return new Response("Nicht gefunden", { status: 404 }); }, }); console.log(`Server läuft auf http://localhost:${server.port}`);

Claude Code generiert diesen Boilerplate in Sekunden und erklärt dabei jeden Parameter: port kann eine Zahl oder eine Umgebungsvariable sein (z.B. parseInt(process.env.PORT ?? "3000")), hostname bestimmt das Interface-Binding.

Claude Code Tipp: Frage Claude Code: "Erstelle einen Bun-Server mit Graceful Shutdown und Health-Check-Endpoint." Claude ergänzt automatisch process.on("SIGTERM", () => server.stop()) und einen /health-Endpunkt mit Zeitstempel.

Request Request-Body parsen — JSON, FormData, Text

async fetch(req: Request) { if (req.method === "POST" && req.url.includes("/api/data")) { // JSON Body const body = await req.json(); // FormData (Multipart) // const form = await req.formData(); // const name = form.get("name"); // Roher Text // const text = await req.text(); // ArrayBuffer (Binary) // const buffer = await req.arrayBuffer(); return Response.json({ received: body, ok: true }); } }

2. Routing-Muster — URL-Matching, Methoden, RegExp

Bun hat kein eingebautes Router-Framework — aber mit einfachen Patterns lassen sich saubere Routing-Strukturen aufbauen. Claude Code empfiehlt drei Ansätze je nach Komplexität: Switch-Statement, Map-basiertes Routing und RegExp-Routen.

Routing Methoden- und Pfad-Matching mit URL

type Handler = (req: Request, url: URL) => Response | Promise<Response>; const routes: Map<string, Handler> = new Map([ ["GET /", () => Response.json({ page: "home" })], ["GET /api/users", getUsers], ["POST /api/users", createUser], ["DELETE /api/users",deleteUser], ]); async fetch(req: Request) { const url = new URL(req.url); const key = `${req.method} ${url.pathname}`; const handler = routes.get(key); if (handler) return handler(req, url); return new Response("Not Found", { status: 404 }); }

RegExp Dynamische Pfadparameter mit regulären Ausdrücken

// Dynamische Routen: /api/users/:id const userRoute = new RegExp('^/api/users/(?<id>[^/]+)$'); const postRoute = new RegExp('^/api/posts/(?<slug>[a-z0-9-]+)$'); async fetch(req: Request) { const url = new URL(req.url); const path = url.pathname; // User by ID let match = path.match(userRoute); if (match && req.method === "GET") { const { id } = match.groups!; return Response.json({ userId: id }); } // Post by Slug match = path.match(postRoute); if (match && req.method === "GET") { const { slug } = match.groups!; return Response.json({ slug }); } return new Response("Not Found", { status: 404 }); }

Claude Code schlägt für größere Projekte leichtgewichtige Router wie Hono vor, das nativ mit Bun kompatibel ist und typsichere Pfadparameter bietet — ohne den Overhead von Express.

Middleware Einfache Middleware-Kette in Bun

type Middleware = (req: Request, next: () => Promise<Response>) => Promise<Response>; const cors: Middleware = async (req, next) => { const res = await next(); res.headers.set("Access-Control-Allow-Origin", "*"); return res; }; const logger: Middleware = async (req, next) => { const start = Date.now(); const res = await next(); console.log(`${req.method} ${req.url} → ${res.status} (${Date.now() - start}ms)`); return res; }; const compose = (...middlewares: Middleware[]) => (req: Request, handler: (r: Request) => Promise<Response>) => middlewares.reduceRight( (next, mw) => () => mw(req, next), () => handler(req) )();

3. WebSocket-Support in Bun — Eingebaut, kein ws-Package nötig

Bun hat WebSocket-Unterstützung direkt in Bun.serve() integriert. Kein separates ws-Package, kein Socket.io nötig. Der Server unterscheidet automatisch zwischen HTTP-Requests und WebSocket-Upgrades.

WebSocket Basis-WebSocket-Server mit Pub/Sub

const server = Bun.serve({ port: 3000, async fetch(req, server) { const url = new URL(req.url); // WebSocket Upgrade if (url.pathname === "/ws") { const upgraded = server.upgrade(req, { data: { userId: url.searchParams.get("userId") ?? "anon", connectedAt: Date.now(), }, }); if (!upgraded) { return new Response("WebSocket upgrade fehlgeschlagen", { status: 400 }); } return; // Bun übernimmt ab hier } return new Response("HTTP Server aktiv"); }, websocket: { // Client verbindet sich open(ws) { console.log(`[WS] ${ws.data.userId} verbunden`); ws.subscribe("broadcast"); // Pub/Sub Channel beitreten ws.send(JSON.stringify({ type: "welcome", userId: ws.data.userId })); }, // Nachricht empfangen message(ws, message) { const data = JSON.parse(message as string); if (data.type === "broadcast") { // An alle Subscriber im Channel senden server.publish("broadcast", JSON.stringify({ from: ws.data.userId, text: data.text, ts: Date.now(), })); } else if (data.type === "ping") { ws.send(JSON.stringify({ type: "pong" })); } }, // Client trennt Verbindung close(ws, code, reason) { console.log(`[WS] ${ws.data.userId} getrennt: ${code}`); ws.unsubscribe("broadcast"); }, // Konfiguration maxPayloadLength: 16 * 1024, // 16 KB max idleTimeout: 120, // 2 Min. Timeout backpressureLimit: 1024, }, });
Pub/Sub ohne Redis: Buns eingebautes Pub/Sub funktioniert innerhalb eines Prozesses ohne externe Message-Broker. Für Multi-Instance-Deployments kann man Redis als Pub/Sub-Backend vorschalten — aber für Single-Server-Setups ist das native System ausreichend und deutlich schneller.

WS Client WebSocket-Client in Bun (für Tests und Bots)

// Bun hat auch einen nativen WS-Client const ws = new WebSocket("ws://localhost:3000/ws?userId=test-bot"); ws.onopen = () => { console.log("Verbunden"); ws.send(JSON.stringify({ type: "broadcast", text: "Hallo von Bot!" })); }; ws.onmessage = (event) => { const msg = JSON.parse(event.data); console.log(`[${msg.from}]: ${msg.text}`); }; ws.onerror = (err) => console.error("WS Fehler:", err);

4. Statische Dateien und File-Serving

Bun kann statische Dateien direkt über Bun.file() ausliefern — ohne Streaming-Overhead und mit korrekten Content-Type-Headers. Das ist erheblich effizienter als das manuelle Lesen via fs.readFile().

Static Statische Dateien mit Bun.file() ausliefern

import { join } from "path"; const PUBLIC_DIR = join(import.meta.dir, "public"); // MIME-Type Mapping const MIME: Record<string, string> = { ".html": "text/html; charset=utf-8", ".css": "text/css", ".js": "application/javascript", ".json": "application/json", ".png": "image/png", ".jpg": "image/jpeg", ".svg": "image/svg+xml", ".woff2":"font/woff2", }; async fetch(req: Request) { const url = new URL(req.url); let filePath = join(PUBLIC_DIR, url.pathname); // Directory → index.html if (filePath.endsWith("/")) filePath += "index.html"; const file = Bun.file(filePath); // Existiert die Datei? if (!(await file.exists())) { return new Response("Not Found", { status: 404 }); } // Extension ermitteln const ext = filePath.slice(filePath.lastIndexOf(".")); const contentType = MIME[ext] ?? "application/octet-stream"; // Bun streamt die Datei direkt — kein Buffer nötig return new Response(file, { headers: { "Content-Type": contentType, "Cache-Control": "public, max-age=3600", }, }); }

Upload Datei-Upload mit Bun verarbeiten

if (req.method === "POST" && url.pathname === "/upload") { const formData = await req.formData(); const file = formData.get("file") as File | null; if (!file) { return Response.json({ error: "Keine Datei" }, { status: 400 }); } // Datei direkt schreiben — kein Buffer nötig const savePath = `/uploads/${crypto.randomUUID()}-${file.name}`; await Bun.write(savePath, file); return Response.json({ success: true, path: savePath, size: file.size, type: file.type, }); }

Bun.write() ist deutlich schneller als Node.js fs.writeFile(), weil Bun intern direkt auf System-Calls wie sendfile() zurückgreift — ohne JavaScript-Buffer als Zwischenschicht.

5. Bun + SQLite — Native Datenbank ohne Treiber-Overhead

Das Killer-Feature für kleine bis mittlere Backend-Services: Bun hat SQLite nativ eingebaut. Kein npm-Package, kein native addon, kein Build-Step. Die bun:sqlite-API ist synchron, blitzschnell und unterstützt Prepared Statements.

SQLite Datenbank einrichten und erste Abfragen

import { Database } from "bun:sqlite"; // Datenbankdatei öffnen (wird erstellt falls nicht vorhanden) const db = new Database("app.db", { create: true, strict: true }); // WAL-Modus für bessere Concurrent-Read-Performance db.run("PRAGMA journal_mode = WAL;"); db.run("PRAGMA synchronous = NORMAL;"); db.run("PRAGMA cache_size = 10000;"); // Schema erstellen db.run(` CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, email TEXT NOT NULL UNIQUE, name TEXT NOT NULL, role TEXT NOT NULL DEFAULT 'user', created INTEGER NOT NULL DEFAULT (unixepoch()), active INTEGER NOT NULL DEFAULT 1 ); CREATE INDEX IF NOT EXISTS idx_users_email ON users(email); `); // In-Memory DB für Tests // const db = new Database(":memory:");

Prepared Prepared Statements für Production-Code

// Statements einmal vorbereiten — dann tausende Male wiederverwenden const stmts = { findByEmail: db.prepare("SELECT * FROM users WHERE email = $email AND active = 1"), findAll: db.prepare("SELECT id, name, email, role FROM users WHERE active = 1 LIMIT $limit OFFSET $offset"), insert: db.prepare("INSERT INTO users (email, name, role) VALUES ($email, $name, $role)"), deactivate: db.prepare("UPDATE users SET active = 0 WHERE id = $id"), count: db.prepare("SELECT COUNT(*) as total FROM users WHERE active = 1"), }; // Einzelnen User abrufen function getUserByEmail(email: string) { return stmts.findByEmail.get({ $email: email }); } // Liste mit Paginierung function listUsers(page = 1, limit = 20) { const offset = (page - 1) * limit; const users = stmts.findAll.all({ $limit: limit, $offset: offset }); const { total } = stmts.count.get() as { total: number }; return { users, total, page, pages: Math.ceil(total / limit) }; } // Transaction für atomare Operationen const createUser = db.transaction((email: string, name: string, role = "user") => { const existing = stmts.findByEmail.get({ $email: email }); if (existing) throw new Error(`Email bereits vergeben: ${email}`); stmts.insert.run({ $email: email, $name: name, $role: role }); return { ok: true, email }; });

API SQLite-CRUD-Endpunkte im HTTP-Server

// GET /api/users?page=1&limit=20 if (url.pathname === "/api/users" && req.method === "GET") { const page = parseInt(url.searchParams.get("page") ?? "1"); const limit = parseInt(url.searchParams.get("limit") ?? "20"); return Response.json(listUsers(page, limit)); } // POST /api/users — User anlegen if (url.pathname === "/api/users" && req.method === "POST") { try { const { email, name, role } = await req.json(); const result = createUser(email, name, role); return Response.json(result, { status: 201 }); } catch (err: any) { return Response.json({ error: err.message }, { status: 409 }); } }
Wichtig: Bun:sqlite ist synchron. Das ist gewollt — SQLite-Zugriffe sind so schnell (Mikrosekunden), dass async-Overhead mehr schadet als nützt. Für concurrent-heavy Workloads mit vielen gleichzeitigen Schreibvorgängen empfiehlt Claude Code stattdessen PostgreSQL via postgres-Package.

6. Performance vs. Node.js und Deno — Die Zahlen 2026

Benchmarks polarisieren — deshalb hier konkrete Messwerte aus dem Bun 1.2-Benchmark-Report (Q1 2026) sowie Community-Benchmarks von TechEmpower Framework Benchmarks Round 24. Gemessen mit wrk (8 Threads, 100 Connections, 30 Sekunden) auf identischer Hardware.

Runtime Framework Req/sec (JSON) Latenz p50 Latenz p99 RAM (idle)
Bun 1.2 Bun.serve (native) 248.000 0,4 ms 1,2 ms 18 MB
Bun 1.2 Hono on Bun 221.000 0,5 ms 1,4 ms 22 MB
Deno 2.x Deno.serve (native) 189.000 0,6 ms 2,1 ms 24 MB
Deno 2.x Hono on Deno 171.000 0,7 ms 2,4 ms 28 MB
Node.js 22 Fastify 5 98.000 1,2 ms 4,8 ms 42 MB
Node.js 22 Express 5 54.000 2,1 ms 9,3 ms 48 MB
Node.js 22 native http 82.000 1,4 ms 5,2 ms 38 MB

Architektur Warum Bun so viel schneller ist

Vier Faktoren machen den Unterschied:

  1. JavaScriptCore statt V8: JSC hat einen 3-stufigen JIT-Compiler (LLInt → Baseline → DFG/FTL) der bei kurzlebigen Funktionen aggressiver optimiert als V8.
  2. Zig als Systemsprache: Bun ist in Zig geschrieben — keine GC-Pausen, präzise Speicherkontrolle, direkte POSIX-Syscalls ohne libuv-Overhead.
  3. Natives HTTP-Parsing: Bun nutzt llhttp direkt ohne Abstraktionsschicht — derselbe Parser wie Node.js, aber ohne den JavaScript-Overhead drumherum.
  4. Keine Module-Fragmentierung: Bun lädt Node.js-kompatible Module direkt ohne den CJS/ESM-Konvertierungsoverhead den Node.js beim Mix beider Formate erzeugt.

Startup Kaltstart-Vergleich — wichtig für Serverless

# Kaltstart messen (time bun run server.ts & sleep 0.1 && kill %1) # Bun 1.2: ~8ms Kaltstart # Deno 2.x: ~22ms Kaltstart # Node.js 22: ~95ms Kaltstart # Node.js 22 + tsx: ~180ms Kaltstart # Bun braucht keinen Build-Step für TypeScript! # TypeScript → direkt ausführen (kein tsc, kein ts-node) bun run server.ts # läuft sofort, ohne Compilation # Node.js braucht entweder: # tsc && node dist/server.js (Build-Step) # oder tsx server.ts (+180ms Overhead)

Für KI-Backend-Services bedeutet das konkret: Ein Bun-basierter Webhook-Handler, der Claude API-Calls verarbeitet, kann 2,5x mehr gleichzeitige Anfragen abarbeiten als das Node.js+Express-Äquivalent — auf derselben Maschine, ohne horizontale Skalierung.

Claude Code + Bun: Der Workflow in der Praxis

Was macht Claude Code konkret mit Bun-Projekten? Hier drei typische Szenarien aus realen Projekten.

Praxis Szenario 1: API-Gateway für Claude-Calls

// Claude Code schreibt dieses Pattern für Rate-Limited AI APIs import Anthropic from "@anthropic-ai/sdk"; const client = new Anthropic(); const rateLimit = new Map<string, number[]>(); const checkRateLimit = (ip: string, max = 10, windowMs = 60_000): boolean => { const now = Date.now(); const hits = (rateLimit.get(ip) ?? []).filter(t => now - t < windowMs); if (hits.length >= max) return false; rateLimit.set(ip, [...hits, now]); return true; }; Bun.serve({ port: 4000, async fetch(req) { const ip = req.headers.get("x-forwarded-for") ?? "local"; if (!checkRateLimit(ip)) { return Response.json( { error: "Rate limit erreicht" }, { status: 429, headers: { "Retry-After": "60" }} ); } const { prompt } = await req.json(); const msg = await client.messages.create({ model: "claude-opus-4-5", max_tokens: 1024, messages: [{ role: "user", content: prompt }], }); return Response.json({ reply: msg.content[0].text }); }, });

Praxis Szenario 2: Echtzeit-Log-Streaming via WebSocket

// Log-Streaming: Tail -f über WebSocket import { watch } from "fs"; const subscribers = new Set<WebSocket>(); // Datei beobachten und Änderungen broadcasten watch("/var/log/app.log", () => { const content = Bun.file("/var/log/app.log"); // Letzten 1KB lesen (neue Zeilen) content.slice(-1024).text().then(text => { for (const ws of subscribers) { ws.send(JSON.stringify({ type: "log", data: text })); } }); }); Bun.serve({ port: 5000, fetch(req, server) { if (server.upgrade(req)) return; return new Response("Log Streaming Server"); }, websocket: { open(ws) { subscribers.add(ws); }, close(ws) { subscribers.delete(ws); }, message() { /* Keine Client-Messages erwartet */ }, }, });

Praxis Szenario 3: SQLite-basiertes Session-Management

import { Database } from "bun:sqlite"; const db = new Database(":memory:"); // In-Memory für Sessions db.run(` CREATE TABLE sessions ( token TEXT PRIMARY KEY, userId INTEGER NOT NULL, data TEXT NOT NULL DEFAULT '{}', expires INTEGER NOT NULL ) `); const sess = { create: db.prepare("INSERT INTO sessions VALUES ($token, $userId, '{}', $expires)"), get: db.prepare("SELECT * FROM sessions WHERE token = $token AND expires > $now"), del: db.prepare("DELETE FROM sessions WHERE token = $token"), prune: db.prepare("DELETE FROM sessions WHERE expires <= $now"), }; // Abgelaufene Sessions alle 10 Min. bereinigen setInterval(() => sess.prune.run({ $now: Date.now() }), 10 * 60 * 1000); function createSession(userId: number): string { const token = crypto.randomUUID(); const expires = Date.now() + 24 * 60 * 60 * 1000; // 24h sess.create.run({ $token: token, $userId: userId, $expires: expires }); return token; } function getSession(token: string) { return sess.get.get({ $token: token, $now: Date.now() }); }

Bun in Production — Was Claude Code dir nicht sagt (aber sollte)

Bun ist schnell — aber es gibt Fallstricke, die Claude Code 2026 kennt und proaktiv addressiert.

Limits Wann Bun die falsche Wahl ist

Claude Code empfiehlt Node.js oder Go wenn:

  • CPU-intensive Workloads: Bun ist kein Wundermittel bei reiner CPU-Last (Matrix-Operationen, Bildverarbeitung) — dort hilft Worker Threads oder ein Go-Service.
  • Legacy-Packages: Manche Node.js-Packages mit nativen Node-API-Bindungen (N-API/nan) funktionieren in Bun noch nicht vollständig (Stand 2026).
  • Multi-Process-Clustering: Bun hat kein stabiles Cluster-Modul wie Node.js. Für horizontale Skalierung auf einem Host lieber mehrere Bun-Prozesse hinter einem Load-Balancer.
Claude Code Produktions-Checkliste für Bun-Server:
1. PRAGMA journal_mode = WAL für SQLite aktivieren.
2. Graceful Shutdown implementieren (SIGTERM + server.stop()).
3. Fehlerbehandlung mit Try/Catch in jedem Route-Handler.
4. Umgebungsvariablen via Bun.env statt process.env für bessere TypeScript-Typisierung.
5. Docker-Image: oven/bun:1.2-alpine — nur 70MB Basis-Image.

Docker Produktions-Dockerfile für Bun

# Dockerfile — Multi-stage Build FROM oven/bun:1.2-alpine AS base WORKDIR /app # Dependencies FROM base AS deps COPY package.json bun.lockb ./ RUN bun install --frozen-lockfile --production # Runtime FROM base AS runtime COPY --from=deps /app/node_modules ./node_modules COPY . . # Non-root User RUN addgroup -S app && adduser -S app -G app USER app EXPOSE 3000 CMD ["bun", "run", "server.ts"] # Build: docker build -t my-bun-api . # Run: docker run -p 3000:3000 -e PORT=3000 my-bun-api

Runtime-Modul im Kurs

Im Claude Code Mastery Kurs: Bun, Deno und Node.js im Vergleich — mit vollständigen Projekten, Performance-Benchmarks und Production-Deployment. Inklusive SQLite-Backend, WebSocket-Chat und Docker-Deployment auf einem VPS.

14 Tage kostenlos testen →