Serverless Functions mit Claude Code: Lambda und Workers
Leitfaden fuer serverless functions mit Claude Code: Plattformwahl, Secrets, Idempotenz, Retry, Tests und Deploy-Check.
Serverless functions sind kurze Codeeinheiten, die pro Event oder HTTP Request laufen, ohne dass du einen dauerhaft laufenden Server verwaltest. Sie passen gut fuer Webhooks, kleine APIs, Bild- oder CSV-Einstiege und Edge-Routing. Trotzdem bleiben Timeout, Retry, Secrets, Rechte, Logs und Kosten echte Produktionsentscheidungen.
Claude Code ist hier hilfreich, weil Handler, Event-Fixture, Tests, Deployment-Notizen und Review-Checkliste im selben Kontext entstehen koennen. Der sichere Ablauf ist nicht, Claude Code blind deployen zu lassen. Der sichere Ablauf ist: Anforderungen schreiben, Runtime waehlen, Event lokal reproduzieren, Konfiguration und Secrets trennen, Idempotenz planen, Fehler testen und erst danach Exposure und Kosten menschlich pruefen.
Nutze die offiziellen Quellen als Basis: AWS Lambda Documentation, Lambda mit Node.js, Cloudflare Workers development and testing und Workers get started guide. Intern passen dazu der AWS Lambda Guide, der Cloudflare Workers Guide, der API Development Guide und der Secrets Management Guide.
Erst Den Use Case Klaeren
Serverless funktioniert am besten, wenn Arbeit kurz, ereignisfoermig und sicher wiederholbar ist.
| Use Case | Warum passend | Claude Code kann entwerfen | Menschlicher Review |
|---|---|---|---|
| Payment- oder Formular-Webhook | Ein Request wird zu einem Event | Signaturcheck, Fixture, Fehlerantworten | Secrets, doppelte Events, Replay-Regeln |
| Bildresize oder CSV-Import | Schwere Arbeit kann in Storage/Queue wandern | Validierung, Job-ID, JSON-Logs | Dateigroessen, Timeout, Cleanup |
| Interne JSON API | Kein dauerhafter App-Server fuer kleine Endpunkte | Handler, Tests, Route | Auth, CORS, Exposure, Rate Limit |
| Edge Redirects und Cache | Antwort nahe am Nutzer | Worker Route, Cache Header, Rollout-Notizen | Cache Purge, personenbezogene Daten, SEO |
flowchart LR
A[Requirements Prompt] --> B[Lambda oder Workers waehlen]
B --> C[Event lokal reproduzieren]
C --> D[Env und Secrets trennen]
D --> E[Idempotenz und Retry planen]
E --> F[Tests ausfuehren]
F --> G[In dev deployen]
G --> H[Logs und Cleanup pruefen]
Ein Guter Prompt Fuer Claude Code
Gib nicht nur den Feature-Namen weiter. Gib Eingaben, Ausgaben, Fehlerverhalten und menschliche Entscheidungsstellen an.
Erstelle eine minimale serverless function in Node.js.
Ziel:
- POST /orders verarbeiten und eine accepted response zurueckgeben
- Lokal mit node local-test.mjs pruefbar sein
- AWS Lambda HTTP API v2 events annehmen
Anforderungen:
- index.mjs, events/create-order.json, local-test.mjs und index.test.mjs erklaeren
- Ohne idempotency-key header 400 zurueckgeben
- Bei gleichem idempotency-key dieselbe response zurueckgeben
- Invalid JSON, invalid input und unsupported route unterscheiden
- Logs als JSON, aber ohne Secrets oder personenbezogene Daten
- Deployment checklist ausgeben
Einschraenkungen:
- Keine externen npm packages
- Production idempotency nutzt DynamoDB, KV oder einen anderen durable store
- IAM, public URLs und kostenpflichtige Ressourcen brauchen human confirmation
Lambda Oder Workers?
AWS Lambda passt, wenn du AWS Events, IAM, S3, DynamoDB, SQS oder EventBridge brauchst. Cloudflare Workers passt, wenn die Hauptaufgabe HTTP am Edge ist: Redirects, leichte APIs, Cache-Regeln oder KV/D1/R2-Flows. Vercel Functions sind fuer Next.js-nahe APIs sinnvoll, aber die Beispiele hier konzentrieren sich auf Lambda und Workers.
| Kriterium | AWS Lambda | Cloudflare Workers |
|---|---|---|
| Staerke | AWS Integration, Business APIs, async jobs | Edge HTTP, Routing, Cache, leichte APIs |
| Lokal | Node.js, SAM, AWS CLI | Wrangler |
| Rechte | IAM role und policy | Bindings, Secrets, Accountrechte |
| Risiko | Zu breite IAM-Rechte, VPC/NAT-Kosten, Logs | Binding drift, Runtime limits, KV consistency |
Lokal Ausfuehrbarer Lambda Handler
// index.mjs
import crypto from "node:crypto";
const localIdempotencyStore = new Map();
function json(statusCode, body) {
return {
statusCode,
headers: { "content-type": "application/json" },
body: JSON.stringify(body),
};
}
function readHeader(headers = {}, name) {
const target = name.toLowerCase();
const found = Object.entries(headers).find(([key]) => key.toLowerCase() === target);
return found?.[1];
}
function parseBody(event) {
if (!event.body) return {};
const raw = event.isBase64Encoded
? Buffer.from(event.body, "base64").toString("utf8")
: event.body;
return JSON.parse(raw);
}
export async function handler(event = {}, context = {}) {
const method = event.requestContext?.http?.method ?? event.httpMethod ?? "GET";
const path = event.rawPath ?? event.path ?? "/";
const requestId = context.awsRequestId ?? crypto.randomUUID();
console.log(JSON.stringify({ level: "info", message: "request.start", requestId, method, path }));
if (method !== "POST" || path !== "/orders") {
return json(404, { error: "not_found" });
}
const idempotencyKey = readHeader(event.headers, "idempotency-key");
if (!idempotencyKey) {
return json(400, { error: "idempotency_key_required" });
}
if (localIdempotencyStore.has(idempotencyKey)) {
return json(200, { ...localIdempotencyStore.get(idempotencyKey), replay: true });
}
let body;
try {
body = parseBody(event);
} catch {
return json(400, { error: "invalid_json" });
}
if (!Number.isFinite(body.amount) || body.amount <= 0 || typeof body.currency !== "string") {
return json(400, { error: "invalid_order" });
}
const accepted = {
orderId: crypto.randomUUID(),
status: "accepted",
amount: body.amount,
currency: body.currency,
};
localIdempotencyStore.set(idempotencyKey, accepted);
console.log(JSON.stringify({ level: "info", message: "order.accepted", requestId, orderId: accepted.orderId }));
return json(202, accepted);
}
{
"version": "2.0",
"routeKey": "POST /orders",
"rawPath": "/orders",
"headers": {
"content-type": "application/json",
"idempotency-key": "demo-key-001"
},
"requestContext": {
"http": {
"method": "POST",
"path": "/orders"
}
},
"body": "{\"amount\":3200,\"currency\":\"EUR\"}",
"isBase64Encoded": false
}
// local-test.mjs
import { readFile } from "node:fs/promises";
import { handler } from "./index.mjs";
const eventPath = process.argv[2] ?? "events/create-order.json";
const event = JSON.parse(await readFile(eventPath, "utf8"));
const first = await handler(event, { awsRequestId: "local-001" });
const second = await handler(event, { awsRequestId: "local-002" });
console.log("first:", first.statusCode, first.body);
console.log("second:", second.statusCode, second.body);
node local-test.mjs events/create-order.json
Die Map ist nur fuer lokale Demos gedacht. In Production gehoert Idempotenz in DynamoDB conditional writes, eine eindeutige Datenbank-Constraint, Cloudflare KV/D1 oder einen anderen dauerhaften Store.
Tests Vor Dem Deploy
// index.test.mjs
import crypto from "node:crypto";
import test from "node:test";
import assert from "node:assert/strict";
import { handler } from "./index.mjs";
function event(overrides = {}) {
return {
rawPath: "/orders",
headers: { "idempotency-key": crypto.randomUUID() },
requestContext: { http: { method: "POST" } },
body: JSON.stringify({ amount: 1200, currency: "EUR" }),
isBase64Encoded: false,
...overrides,
};
}
test("requires idempotency-key", async () => {
const result = await handler(event({ headers: {} }), {});
assert.equal(result.statusCode, 400);
});
test("accepts a valid order", async () => {
const result = await handler(event(), {});
assert.equal(result.statusCode, 202);
assert.equal(JSON.parse(result.body).status, "accepted");
});
test("rejects invalid JSON", async () => {
const result = await handler(event({ body: "not-json" }), {});
assert.equal(result.statusCode, 400);
});
node --test index.test.mjs
Workers Mit KV Binding
// src/worker.js
export default {
async fetch(request, env) {
const url = new URL(request.url);
if (request.method !== "POST" || url.pathname !== "/orders") {
return Response.json({ error: "not_found" }, { status: 404 });
}
if (request.headers.get("x-webhook-secret") !== env.WEBHOOK_SECRET) {
return Response.json({ error: "unauthorized" }, { status: 401 });
}
const idempotencyKey = request.headers.get("idempotency-key");
if (!idempotencyKey) {
return Response.json({ error: "idempotency_key_required" }, { status: 400 });
}
const existing = await env.IDEMPOTENCY_KV.get(idempotencyKey, "json");
if (existing) {
return Response.json({ ...existing, replay: true });
}
const body = await request.json();
if (!Number.isFinite(body.amount) || typeof body.currency !== "string") {
return Response.json({ error: "invalid_order" }, { status: 400 });
}
const accepted = {
orderId: crypto.randomUUID(),
status: "accepted",
amount: body.amount,
currency: body.currency,
};
await env.IDEMPOTENCY_KV.put(idempotencyKey, JSON.stringify(accepted), {
expirationTtl: 86400,
});
return Response.json(accepted, { status: 202 });
},
};
npm create cloudflare@latest serverless-orders-worker
cd serverless-orders-worker
npx wrangler kv namespace create IDEMPOTENCY_KV
npx wrangler secret put WEBHOOK_SECRET
npx wrangler dev
Fallstricke Und Checkliste
Der erste Fehler ist die Annahme, dass ein Event genau einmal ausgefuehrt wird. Webhooks, Queues, async Events und Browser koennen wiederholen. Der zweite Fehler ist, Secrets in Logs oder Beispielen zu zeigen. Der dritte Fehler sind breite Rechte wie Resource: "*". Der vierte Fehler ist ein oeffentlicher Endpoint ohne Owner, Auth, CORS, Rate Limit, Log-Retention und Cleanup-Datum.
| Check | Was bestaetigt sein muss |
|---|---|
| Requirements | Input, Output, Owner und Fehlerantworten sind dokumentiert |
| Runtime | Lambda Node.js runtime oder Workers compatibility_date ist explizit |
| Lokal | Fixture und node --test laufen |
| Env/Secrets | Konfiguration und Secrets sind getrennt |
| Idempotenz | Retry erzeugt keine doppelte Buchung oder Erstellung |
| Timeout/Retry | Langsame Arbeit wandert in Queue oder durable job |
| Observability | JSON Logs, Fehlerrate, Alerts und Retention sind definiert |
| Cleanup | Loeschbefehle oder Dashboard-Schritte sind dokumentiert |
zip function.zip index.mjs
aws lambda update-function-code \
--function-name serverless-orders-dev \
--zip-file fileb://function.zip
npx wrangler deploy
Review-Prompt:
Review diese serverless function vor dem Publizieren.
Trenne blocking issues, non-blocking improvements und human confirmations.
Pruefe Idempotenz, timeout/retry, Secrets, IAM oder bindings, Logs,
lokale Reproduzierbarkeit, Cleanup, offizielle Links und interne Links.
ClaudeCodeLab buendelt solche Muster in Claude Code products and templates. Wenn dein Team AWS-Rechte, CLAUDE.md, Review-Prompts und Deployment-Freigaben fuer ein echtes Repository gestalten will, ist die Claude Code consultation and training page der passende naechste Schritt.
Im praktischen Test brachte das Event-Fixture den groessten Gewinn. Claude Code ist schnell, aber Retry, Secrets und Cleanup werden nur verlaesslich, wenn diese Regeln im ersten Prompt stehen.
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.