JavaScript-Entwickler kennen das Problem: Node.js, npm, webpack, Jest, ts-node — jedes Tool eine eigene Konfiguration, jedes Ökosystem eigene Eigenheiten. Bun.js löst das mit einem radikalen Ansatz: eine einzige Binary für Runtime, Package Manager, Bundler, Test Runner und sogar SQLite-Datenbankzugriff. Claude Code erkennt Bun-Projekte automatisch und optimiert seine Vorschläge entsprechend.
🥟 Bun auf einen Blick
- Runtime Ersetzt Node.js mit JavaScriptCore-Engine
- Package Manager Ersetzt npm/yarn/pnpm — bis zu 25× schneller
- Bundler Ersetzt webpack/esbuild — nativ in Bun
- Test Runner Ersetzt Jest/Vitest — kompatible API
- SQLite Eingebaute Datenbank — kein Treiber nötig
1. Bun als Runtime — bun run, Bun.serve() und Vergleich zu Node.js
Bun basiert auf JavaScriptCore (die Engine hinter Safari/WebKit), nicht auf V8 wie Node.js. Das macht Bun in I/O-intensiven Workloads messbar schneller, besonders beim Starten von Prozessen und beim Verarbeiten von HTTP-Anfragen. TypeScript und JSX werden ohne Transpilierung direkt ausgeführt — kein ts-node, kein Babel nötig.
Direkte Skriptausführung
# Node.js benötigt ts-node oder esbuild
npx ts-node server.ts
# Bun führt TypeScript nativ aus
bun run server.ts
# Oder einfach direkt:
bun server.ts
HTTP-Server mit Bun.serve()
// server.ts — TypeScript direkt, kein Build-Schritt
const server = Bun.serve({
port: 3000,
async fetch(req: Request): Promise<Response> {
const url = new URL(req.url);
if (url.pathname === "/health") {
return Response.json({ status: "ok", runtime: "bun" });
}
if (url.pathname === "/api/users") {
const users = db.query("SELECT * FROM users LIMIT 100").all();
return Response.json(users);
}
return new Response("Not Found", { status: 404 });
},
});
console.log(`Server läuft auf http://localhost:${server.port}`);
Benchmark: Bun vs. Node.js vs. Deno
| Benchmark | Node.js 22 | Deno 2 | Bun 1.2 |
|---|---|---|---|
| Startup-Zeit (cold) | 42 ms | 51 ms | 9 ms |
| HTTP req/s (hello world) | 68.000 | 71.000 | 210.000 |
| JSON parse 10 MB | 145 ms | 138 ms | 62 ms |
| npm install (100 Pakete) | 12,4 s | 9,1 s | 0,6 s |
| TypeScript kompilieren | 4,2 s (tsc) | 3,8 s | 0,3 s |
Hinweis für Claude Code: Wenn in deinem Projekt eine bun.lockb vorhanden ist, erkennt Claude Code das automatisch als Bun-Projekt. Claude schlägt dann bun run statt npm run vor und nutzt die Bun-spezifischen APIs in seinen Code-Generierungen.
JSX und TypeScript nativ
// App.tsx — funktioniert direkt mit bun run App.tsx
import { renderToString } from "react-dom/server";
function App({ name }: { name: string }) {
return <h1>Hallo, {name}!</h1>;
}
const html = renderToString(<App name="Welt" />);
console.log(html); // <h1>Hallo, Welt!</h1>
2. Package Manager — bun install, bun.lockb und Workspaces
Buns Package Manager ist der schnellste auf dem Markt. Er verwendet ein binäres Lockfile-Format (bun.lockb), globales Caching und parallele Downloads. Die Kompatibilität zu npm ist vollständig — alle npm-Pakete funktionieren ohne Änderungen.
Grundlegende Befehle
# Alle Abhängigkeiten installieren
bun install
# Paket hinzufügen (Produktion)
bun add express zod
# Dev-Dependency hinzufügen
bun add -d @types/express vitest
# Paket entfernen
bun remove lodash
# Paket aktualisieren
bun update react
# Globale Installation
bun add -g typescript
package.json — Scripts mit bun
{
"name": "my-app",
"scripts": {
"dev": "bun run --hot src/index.ts",
"build": "bun build src/index.ts --outdir dist",
"test": "bun test",
"start": "bun run dist/index.js"
},
"dependencies": {
"hono": "^4.0.0",
"zod": "^3.22.0"
}
}
Workspaces (Monorepo)
# Root package.json
{
"workspaces": ["packages/*", "apps/*"],
"scripts": {
"dev": "bun run --filter '*' dev",
"build": "bun run --filter '*' build"
}
}
# In einem Workspace-Paket installieren
bun add react --cwd apps/web
# Alle Workspaces listen
bun pm ls
npm-Registries und private Pakete
# .bunfig.toml — Bun-Konfiguration
[install]
registry = "https://registry.npmjs.org"
[install.scopes]
"@meinunternehmen" = {
registry = "https://npm.meinunternehmen.de",
token = "$NPM_TOKEN"
}
Lockfile-Tipp: Das binäre bun.lockb ist kleiner und schneller zu parsen als package-lock.json. Claude Code liest es automatisch und kann daraus Abhängigkeitsgraphen ableiten. Bei Konflikten mit Tools die JSON erwarten: bun install --frozen-lockfile für CI.
3. Bun als Bundler — bun build, Minifizierung und Targets
Bun enthält einen vollständigen Bundler ohne externe Abhängigkeiten. Er ist darauf ausgelegt, webpack und esbuild in den meisten Anwendungsfällen zu ersetzen — mit nativ schneller Performance durch JavaScriptCore-Optimierungen.
Grundlegender Build
# Single-Entry-Point Build
bun build src/index.ts --outdir dist
# Minifiziert + Source Maps
bun build src/index.ts --outdir dist --minify --sourcemap=external
# Für Browser (ESM)
bun build src/app.tsx --outdir dist/browser --target browser
# Für Node.js
bun build src/server.ts --outdir dist/server --target node
# Als Bibliothek bauen
bun build src/lib.ts --outdir dist --format esm
Programmgesteuert via API
const result = await Bun.build({
entrypoints: ["./src/index.ts"],
outdir: "./dist",
target: "browser",
minify: {
whitespace: true,
identifiers: true,
syntax: true,
},
sourcemap: "external",
splitting: true, // Code-Splitting für Lazy Loading
plugins: [
bunPlugin() // Eigene Bun-Plugins
],
define: {
"process.env.NODE_ENV": '"production"',
"__VERSION__": '"1.0.0"',
},
});
if (!result.success) {
console.error("Build fehlgeschlagen:", result.logs);
process.exit(1);
}
result.outputs.forEach(out => {
console.log(`${out.path}: ${out.size} Bytes`);
});
Eigene Plugins schreiben
import type { BunPlugin } from "bun";
const yamlPlugin: BunPlugin = {
name: "yaml-loader",
setup(build) {
build.onLoad({ filter: /\.yaml$/ }, async ({ path }) => {
const text = await Bun.file(path).text();
const data = parseYaml(text); // eigene Parse-Funktion
return {
contents: `export default ${JSON.stringify(data)}`,
loader: "js",
};
});
},
};
await Bun.build({
entrypoints: ["./src/index.ts"],
plugins: [yamlPlugin],
});
4. Bun Test Runner — bun:test, Mocking und Coverage
bun:test ist ein Jest-kompatibler Test Runner mit identischer API. Bestehende Jest-Tests laufen ohne Änderungen. Die Performance übertrifft Jest und Vitest deutlich, weil keine Transpilierung nötig ist.
Tests schreiben
import { describe, test, expect, beforeEach, afterAll } from "bun:test";
describe("UserService", () => {
let service: UserService;
beforeEach(() => {
service = new UserService(":memory:");
});
test("erstellt einen neuen User", () => {
const user = service.create({ name: "Alice", email: "alice@example.com" });
expect(user.id).toBeGreaterThan(0);
expect(user.name).toBe("Alice");
});
test("wirft bei doppelter E-Mail", () => {
service.create({ name: "Alice", email: "a@a.de" });
expect(() => {
service.create({ name: "Bob", email: "a@a.de" });
}).toThrow("E-Mail bereits vergeben");
});
test("findet User nach ID", async () => {
const created = service.create({ name: "Carol", email: "c@c.de" });
const found = service.findById(created.id);
expect(found).toEqual(expect.objectContaining({ name: "Carol" }));
});
});
Mocking mit bun:test
import { mock, spyOn } from "bun:test";
// Funktion mocken
const fetchUser = mock(async (id: number) => ({
id,
name: "Mock User",
email: "mock@example.com",
}));
test("ruft fetchUser einmal auf", async () => {
const user = await fetchUser(42);
expect(fetchUser).toHaveBeenCalledTimes(1);
expect(fetchUser).toHaveBeenCalledWith(42);
expect(user.name).toBe("Mock User");
});
// Modul-Mock
const consoleSpy = spyOn(console, "log");
test("loggt die richtige Nachricht", () => {
greet("Welt");
expect(consoleSpy).toHaveBeenCalledWith("Hallo, Welt!");
});
Tests ausführen und Coverage
# Alle Tests ausführen
bun test
# Mit Coverage
bun test --coverage
# Nur bestimmte Dateien
bun test src/users.test.ts
# Watch-Mode
bun test --watch
# Timeout setzen (ms)
bun test --timeout 5000
Performance-Vergleich
| Test-Runner | 500 Tests (cold) | 500 Tests (warm) | Watch-Restart |
|---|---|---|---|
| Jest 29 | 14,2 s | 8,1 s | 2,8 s |
| Vitest 2 | 6,4 s | 3,2 s | 0,9 s |
| bun:test | 1,1 s | 0,4 s | 0,15 s |
5. Bun SQLite — eingebaute Datenbank ohne Treiber
bun:sqlite ist direkt in die Bun-Binary integriert. Kein npm install better-sqlite3, keine nativen Addons, keine Kompilierung. Die API ist identisch zu better-sqlite3 — Code lässt sich direkt portieren.
Grundlegende Verwendung
import { Database } from "bun:sqlite";
// Datenbank öffnen (oder erstellen)
const db = new Database("meine-app.sqlite");
// WAL-Mode aktivieren (empfohlen für Performance)
db.exec("PRAGMA journal_mode = WAL;");
db.exec("PRAGMA foreign_keys = ON;");
// Tabelle erstellen
db.exec(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
`);
Prepared Statements und Abfragen
// Prepared Statement — einmal vorbereiten, oft nutzen
const insertUser = db.prepare(
"INSERT INTO users (name, email) VALUES ($name, $email)"
);
const findById = db.prepare(
"SELECT * FROM users WHERE id = $id"
);
const findAll = db.prepare(
"SELECT * FROM users ORDER BY created_at DESC LIMIT $limit"
);
// Einfügen
const { lastInsertRowid } = insertUser.run({
$name: "Alice",
$email: "alice@example.com",
});
// Einen Datensatz abrufen
const user = findById.get({ $id: lastInsertRowid });
console.log(user); // { id: 1, name: 'Alice', ... }
// Mehrere Datensätze abrufen
const users = findAll.all({ $limit: 50 });
console.log(`${users.length} User gefunden`);
Transaktionen
// Atomare Transaktionen mit db.transaction()
const transferFunds = db.transaction((from: number, to: number, amount: number) => {
const debit = db.prepare(
"UPDATE accounts SET balance = balance - $amount WHERE id = $id"
);
const credit = db.prepare(
"UPDATE accounts SET balance = balance + $amount WHERE id = $id"
);
debit.run({ $amount: amount, $id: from });
credit.run({ $amount: amount, $id: to });
return { success: true, transferred: amount };
});
// Ausführen — automatisches Rollback bei Fehler
const result = transferFunds(1, 2, 500);
console.log(result.transferred); // 500
In-Memory-Datenbank für Tests
import { Database } from "bun:sqlite";
import { describe, test, expect, beforeEach } from "bun:test";
describe("Database Tests", () => {
let db: Database;
beforeEach(() => {
// Jeder Test bekommt eine frische In-Memory-DB
db = new Database(":memory:");
db.exec(schema); // Tabellen erstellen
});
test("speichert und liest User", () => {
db.prepare("INSERT INTO users (name) VALUES (?)").run("Test");
const user = db.prepare("SELECT * FROM users").get();
expect(user.name).toBe("Test");
});
});
6. Migration und Kompatibilität — von Node.js zu Bun
Bun hat Node.js-API-Kompatibilität als Ziel, aber es gibt Lücken. Die meisten Express/Fastify/Prisma-Apps laufen ohne Änderungen. Native Node.js-Addons (.node-Dateien) sind die größte Einschränkung.
Was funktioniert reibungslos
// Diese Node.js-APIs funktionieren in Bun identisch:
import fs from "fs"; // ✅ Komplett
import path from "path"; // ✅ Komplett
import http from "http"; // ✅ Komplett
import crypto from "crypto"; // ✅ Komplett
import stream from "stream"; // ✅ Größtenteils
import os from "os"; // ✅ Komplett
import events from "events"; // ✅ Komplett
import buffer from "buffer"; // ✅ Komplett
// process.env funktioniert identisch
const port = process.env.PORT ?? "3000";
const nodeEnv = process.env.NODE_ENV;
Bun-spezifische Umgebungsvariablen
// .env wird automatisch geladen — kein dotenv nötig!
// .env.local, .env.development, .env.production werden ebenfalls geladen
// Zugriff identisch zu Node.js:
const dbUrl = process.env.DATABASE_URL;
const apiKey = process.env.API_KEY;
// Bun.env ist ein Alias für process.env
const secret = Bun.env.JWT_SECRET;
⚠️ Bekannte Einschränkungen: Native Node.js-Addons (.node-Dateien) wie bcrypt, sharp oder canvas werden noch nicht vollständig unterstützt. Nutze Alternativen: bcryptjs (pure JS), sharp über Bun-Shell oder Bun.CryptoHasher für Hashing.
Migration eines Express-Servers
// VORHER: Node.js + Express
import express from "express";
const app = express();
app.get("/", (req, res) => res.json({ ok: true }));
app.listen(3000);
// NACHHER: Bun native (kein npm-Paket nötig)
Bun.serve({
port: 3000,
fetch(req) {
const url = new URL(req.url);
if (url.pathname === "/") return Response.json({ ok: true });
return new Response("Not Found", { status: 404 });
},
});
// ODER: Hono (Express-ähnlich, Bun-optimiert)
import { Hono } from "hono";
const app = new Hono();
app.get("/", (c) => c.json({ ok: true }));
export default app; // Bun erkennt das automatisch
Migrationscheckliste
Schritt-für-Schritt Migration
- 1. Bun installieren:
curl -fsSL https://bun.sh/install | bash - 2.
bun installstattnpm installausführen — prüfen ob alle Pakete installieren - 3.
bun run src/index.ts— native Addons identifizieren (Fehler lesen!) - 4. Native Addons durch Pure-JS-Alternativen ersetzen
- 5. Tests mit
bun testausführen — Jest-Tests laufen meist sofort - 6.
dotenvaus package.json entfernen — Bun lädt .env automatisch - 7.
ts-nodeund@types/nodekönnen entfernt werden - 8. Scripts in package.json von
node/ts-nodeaufbunumstellen
Bun mit Claude Code — Workflow-Optimierungen
# Claude Code erkennt bun.lockb und nutzt automatisch Bun-Befehle
# Nützliche Kombination: Bun + Hot Reload während Claude Code entwickelt
# Terminal 1: Server mit Hot Reload
bun run --hot src/server.ts
# Terminal 2: Tests im Watch-Mode
bun test --watch
# Terminal 3: Claude Code für Code-Generierung
claude
# .claude/settings.json — Claude Code für Bun konfigurieren
{
"tools": {
"bash": true
},
"env": {
"BUN_INSTALL": "$HOME/.bun",
"PATH": "$HOME/.bun/bin:$PATH"
}
}
Bun.js mit Claude Code ausprobieren
Starte deinen kostenlosen Trial und erlebe, wie Claude Code Bun-Projekte automatisch erkennt, optimierte Snippets generiert und deine Entwicklungsgeschwindigkeit verdoppelt.
Kostenlos Trial starten →