Cloudflare Workers mit Claude Code: Praxisguide für Edge APIs
Baue eine Workers API mit Claude Code, Wrangler, KV/D1/R2, Cache, Rate Limiting, Logs und Security Headers.
Cloudflare Workers führt JavaScript und TypeScript im Edge-Netzwerk von Cloudflare aus. Edge bedeutet: Der Code läuft näher am Nutzer statt nur in einer zentralen Region. Das passt gut für kleine APIs, Webhooks, BFFs, gecachte Antworten, Sicherheitsprüfungen und kontrollierte Downloads aus R2.
Claude Code ist dafür nützlich, weil ein Worker sauber zerfällt: ein fetch Handler, eine wrangler.toml und explizite Bindings wie KV, D1 und R2. Die offiziellen Quellen wurden am 3. Juni 2026 erneut geprüft: Workers, Wrangler, bindings, KV, D1, R2, Cache API, Rate Limiting und Workers Logs. Für den Vergleich helfen serverless functions und der AWS Lambda guide.
Was wir bauen
Das Beispiel ist eine Orders API. D1 speichert Bestellungen, KV hält kleine Einstellungen, R2 speichert Receipt JSON, Cache API beschleunigt sichere GET-Antworten, Rate Limiting bremst Missbrauch, Workers Logs erfassen strukturierte Logs und jede Antwort bekommt Security Headers.
flowchart LR
Client["Client"] --> Worker["Worker fetch handler"]
Worker --> D1["D1 orders"]
Worker --> KV["KV settings"]
Worker --> R2["R2 receipts"]
Worker --> Cache["Cache API"]
Worker --> Logs["Workers Logs"]
Ein Binding ist eine externe Fähigkeit, die dem Worker unter einem Namen bereitgestellt wird. binding = "DB" wird im Code zu env.DB.
Prompt für Claude Code
Implementiere eine Orders API mit Cloudflare Workers + TypeScript.
Dateien:
- src/index.ts
- wrangler.toml
- schema.sql
Anforderungen:
- Module fetch handler verwenden
- GET /health gibt JSON zurück
- GET /orders/:id liest aus D1 und cached nur sichere öffentliche Ausgabe 30 Sekunden
- POST /orders validiert JSON und schreibt in D1
- Authorization Bearer gegen API_TOKEN prüfen
- SETTINGS KV, DB D1, RECEIPTS R2 und API_RATE_LIMITER verwenden
- Security Headers für jede Antwort setzen
- Logs als JSON Objekte ausgeben
- curl Prüfkommandos ergänzen
Nicht tun:
- Secrets in wrangler.toml schreiben
- Einen dauerhaft laufenden Node.js Server voraussetzen
- Pseudocode liefern
Wrangler Setup
npm create cloudflare@latest claude-worker-api -- --type=hello-world
cd claude-worker-api
npm install -D typescript wrangler
npx wrangler --version
C3 kann in neuen Projekten wrangler.jsonc erzeugen. Wrangler unterstützt JSON/JSONC und TOML; dieser Artikel nutzt TOML wegen der Lesbarkeit. Wenn dein Projekt wrangler.jsonc hat, übernimm dieselben Keys in JSONC.
name = "claude-worker-api"
main = "src/index.ts"
compatibility_date = "2026-06-03"
[vars]
PUBLIC_ENV = "production"
[observability]
enabled = true
head_sampling_rate = 1
[[d1_databases]]
binding = "DB"
database_name = "claude-worker-api"
database_id = "replace-with-d1-database-id"
[[kv_namespaces]]
binding = "SETTINGS"
id = "replace-with-kv-namespace-id"
[[r2_buckets]]
binding = "RECEIPTS"
bucket_name = "claude-worker-receipts"
[[ratelimits]]
name = "API_RATE_LIMITER"
namespace_id = "1001"
[ratelimits.simple]
limit = 60
period = 60
npx wrangler login
npx wrangler d1 create claude-worker-api
npx wrangler kv namespace create SETTINGS
npx wrangler r2 bucket create claude-worker-receipts
npx wrangler secret put API_TOKEN
D1 Schema
CREATE TABLE IF NOT EXISTS orders (
id TEXT PRIMARY KEY,
email TEXT NOT NULL,
amount INTEGER NOT NULL,
status TEXT NOT NULL DEFAULT 'pending',
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_orders_email ON orders(email);
npx wrangler d1 execute claude-worker-api --local --file=./schema.sql
npx wrangler d1 execute claude-worker-api --remote --file=./schema.sql
Lauffähiger Worker
export interface Env {
API_TOKEN: string;
PUBLIC_ENV: string;
DB: D1Database;
SETTINGS: KVNamespace;
RECEIPTS: R2Bucket;
API_RATE_LIMITER: RateLimit;
}
const securityHeaders = {
"content-security-policy": "default-src 'none'; frame-ancestors 'none'",
"x-content-type-options": "nosniff",
"x-frame-options": "DENY",
"referrer-policy": "no-referrer",
"permissions-policy": "camera=(), microphone=(), geolocation=()",
};
function json(data: unknown, init: ResponseInit = {}) {
return new Response(JSON.stringify(data), {
...init,
headers: {
"content-type": "application/json; charset=utf-8",
...securityHeaders,
...init.headers,
},
});
}
export default {
async fetch(request, env, ctx): Promise<Response> {
const url = new URL(request.url);
const requestId = crypto.randomUUID();
console.log({ event: "request_started", requestId, method: request.method, path: url.pathname });
if (url.pathname === "/health") {
const maintenance = await env.SETTINGS.get("maintenance");
return json({ ok: true, env: env.PUBLIC_ENV, maintenance: maintenance === "true" });
}
if (request.headers.get("authorization") !== `Bearer ${env.API_TOKEN}`) {
return json({ error: "unauthorized" }, { status: 401 });
}
const { success } = await env.API_RATE_LIMITER.limit({
key: request.headers.get("authorization")?.slice(-16) ?? "anonymous",
});
if (!success) return json({ error: "rate_limited" }, { status: 429 });
const match = url.pathname.match(/^\/orders\/([a-zA-Z0-9_-]+)$/);
if (request.method === "GET" && match) {
const cacheKey = new Request(url.toString(), { method: "GET" });
const cached = await caches.default.match(cacheKey);
if (cached) return cached;
const order = await env.DB.prepare(
"SELECT id, email, amount, status, created_at FROM orders WHERE id = ?"
).bind(match[1]).first();
if (!order) return json({ error: "not_found" }, { status: 404 });
const response = json({ order }, {
headers: { "cache-control": "public, max-age=30", "cache-tag": `order-${match[1]}` },
});
ctx.waitUntil(caches.default.put(cacheKey, response.clone()));
return response;
}
if (request.method === "POST" && url.pathname === "/orders") {
const body = await request.json<{ email?: string; amount?: number }>();
if (!body.email?.includes("@") || !Number.isInteger(body.amount) || body.amount <= 0) {
return json({ error: "invalid_order" }, { status: 400 });
}
const id = crypto.randomUUID();
await env.DB.prepare(
"INSERT INTO orders (id, email, amount, status) VALUES (?, ?, ?, ?)"
).bind(id, body.email, body.amount, "pending").run();
await env.RECEIPTS.put(`orders/${id}.json`, JSON.stringify({ id, email: body.email, amount: body.amount }), {
httpMetadata: { contentType: "application/json" },
});
console.log({ event: "order_created", requestId, orderId: id });
return json({ id, status: "pending" }, { status: 201 });
}
return json({ error: "not_found" }, { status: 404 });
},
} satisfies ExportedHandler<Env>;
Lokal testen und deployen
printf "API_TOKEN=dev-token\n" > .dev.vars
npx wrangler dev
curl http://localhost:8787/health
curl -X POST http://localhost:8787/orders \
-H "authorization: Bearer dev-token" \
-H "content-type: application/json" \
-d "{\"email\":\"masa@example.com\",\"amount\":1200}"
curl http://localhost:8787/orders/replace-with-created-id \
-H "authorization: Bearer dev-token"
npx wrangler secret put API_TOKEN
npx wrangler d1 execute claude-worker-api --remote --file=./schema.sql
npx wrangler deploy
npx wrangler tail
Praxisfälle
Erstens: Formular- oder Bestell-API. D1 speichert Status, R2 speichert Belege oder Anhänge, Workers Logs helfen bei Betrieb und Debugging.
Zweitens: Webhook Receiver. Signatur prüfen, Event ID in D1 speichern und Duplikate ignorieren.
Drittens: BFF, also Backend for Frontend. Der Worker versteckt API Keys, formt Antworten für die UI um und cached nur sichere Daten.
Viertens: Kontrollierte R2 Downloads. Die Datei liegt in R2, Autorisierung und Logs liegen im Worker.
Häufige Fehler
Schreibe Workers nicht wie dauerhaft laufende Node.js Server. Plane mit Web APIs, Request, Response, fetch, crypto und Bindings.
Vermische die Storage-Rollen nicht. KV ist für kleine Key-Value-Daten, D1 für relationale Daten, R2 für Objekte und Cache API für kurzlebige Antworten.
Cache keine privaten Antworten. Wenn Cookie, Token, E-Mail oder Nutzerkontext eine Rolle spielen, meide Shared Cache oder nutze sehr bewusste Keys und TTLs.
Rate-limite nicht nur nach IP. API Key, User ID oder Tenant ID sind meist stabiler.
Plattformwahl
Workers passt für latenzarme HTTP APIs nahe am Cloudflare Cache und den Bindings. Pages Functions passt, wenn eine Cloudflare Pages Site etwas dynamische Logik braucht. Cloud Run passt für Container, lange Jobs oder bestehende Frameworks. Lambda passt, wenn der Ablauf um AWS Dienste wie S3, DynamoDB, EventBridge oder SQS gebaut ist.
Review Prompt
Reviewe diese Cloudflare Workers Implementierung.
Prüfe:
- Workers Runtime Kompatibilität
- Gleichheit von Env Typen und Wrangler Bindings
- Secrets in Code, Logs oder Config
- D1 bind Nutzung und SQL Injection Risiko
- unsichere Cache API Nutzung
- Rate Limiting Key
- Security Headers auf jeder Antwort
- fehlende curl Prüfungen
Gib Findings nach Schweregrad, dann Fixes, dann Tests aus.
CTA und Prüfnachweis
Migriere zuerst nur einen Endpoint: /health, einen read-only GET oder einen Webhook. Gib Claude Code Dateien, Binding-Namen, Prüfkommandos und Verbote. Wiederverwendbare Templates findest du unter products; für Teamtraining und Review-Routinen nutze training.
Ergebnis des Praxistests: Den Ablauf aus Wrangler, D1, Worker und curl habe ich für diesen Artikel zusammengeführt; vor Produktion solltest du wrangler deploy, wrangler tail, Headers, Cache und 429-Verhalten in deinem Cloudflare Account erneut prüfen.
Kostenloses PDF: Claude-Code-Cheatsheet
E-Mail eintragen und eine Seite mit Befehlen, Review-Gewohnheiten und sicheren Workflows herunterladen.
Wir schützen Ihre Daten und senden keinen Spam.
Über den Autor
Masa
Engineer für praktische Claude-Code-Workflows und Team-Einführung.
Ähnliche Artikel
Claude Code Workflow von Obsidian zu CLAUDE.md
Obsidian-Arbeitsnotizen in CLAUDE.md-Betriebsnotizen verwandeln und Kontext nicht ständig neu erklären.
Claude Code Revenue CTA Routing: Artikel zu PDF, Gumroad und Beratung führen
Ein Claude-Code-Ablauf, der Leser nach Absicht zu Gratis-PDF, Gumroad oder Beratung führt.
Claude-Code-Team-Handoff-Regeln: Belege, Berechtigungen, Rollback und Umsatzpfade
Ein praktisches Claude-Code-Handoff für Review-Belege, Berechtigungen, Rollback, Gratis-PDF, Gumroad und Beratung.