Das erste Mal, als ich eine JSON-API auf
Cloudflare Workers
deployt habe, habe ich sie um 23 Uhr vom Laptop aus rausgeschickt, und sie lief in über 300 Rechenzentren,
bevor ich meinen Tee zu Ende getrunken hatte. Kein Dockerfile, kein Kubernetes-Cluster, kein Cold-Start-Drama.
Ein einziges wrangler deploy und ein 1,2 KB Bundle. Diese Erfahrung ist der Grund, warum Workers
mein Standard für JSON-rein, JSON-raus-Dienste geworden ist — Webhooks, Proxies, API-Aggregatoren, Edge-Auth.
Wenn 80 % deines Backends "JSON parsen, was machen, JSON zurückgeben" sind, ist das der Artikel, den ich damals
beim Einstieg gerne gehabt hätte.
Ein Cloudflare Worker ist im Grunde eine einzelne JavaScript- (oder TypeScript-)Funktion, die auf V8-Isolates an Cloudflares Edge läuft. Sie empfängt eine Request, gibt eine Response zurück und hat Zugriff auf die Standard-Fetch-API. Wenn du fetch() im Browser benutzt hast, kennst du bereits 90 % der Runtime. Was du nicht kennst, ist das kleine Set an Patterns, das einen Spielzeug-Worker von einem trennt, den du tatsächlich in Produktion laufen lassen kannst. Darum geht es in diesem Artikel.
Dein erster JSON-Endpoint
Hier ist der kleinste brauchbare JSON-Endpoint. Er gibt ein einzelnes Objekt mit Timestamp und Nachricht zurück.
Speichere ihn in einem Wrangler-Projekt als src/index.ts:
export default {
async fetch(request, env, ctx) {
const payload = {
message: 'Hello from the edge',
servedAt: new Date().toISOString(),
colo: request.cf?.colo ?? 'unknown',
};
return Response.json(payload);
},
};Zwei Dinge zu beachten. Erstens: Response.json() ist ein statischer Helper, der das Objekt
serialisiert und Content-Type: application/json für dich setzt. Bau nicht dein eigenes
new Response(JSON.stringify(x)), außer du brauchst einen custom Content-Type — du vergisst irgendwann
eh den Header. Zweitens: request.cf.colo sagt dir, welches Cloudflare-Rechenzentrum den Request bedient.
Ein Request aus Berlin zeigt FRA, aus Tokio NRT. Der ganze "Edge"-Pitch in einem Feld.
Einen JSON-Request-Body parsen
POST-Endpoints müssen einen Body lesen. Die Fetch-API gibt dir request.json(), das den Body-Stream liest und ihn in einem Aufruf parst:
export default {
async fetch(request) {
if (request.method !== 'POST') {
return new Response('Method not allowed', { status: 405 });
}
const body = await request.json();
// body is now a regular JavaScript object
const { email, plan } = body;
return Response.json({
received: { email, plan },
ok: true,
});
},
};Sieht sauber aus, aber dieser Code hat einen Bug, den du innerhalb von 24 Stunden nach dem Deployment triffst:
wenn der Client einen leeren Body oder malformed JSON schickt, wirft request.json() einen
SyntaxError, dein Worker crasht, und Cloudflare gibt eine generische 500 zurück.
Nicht die Antwort, die du vor Kunden stehen haben willst.
Mit malformed JSON umgehen — lass es nicht in einer 500 enden
Wickel Body-Parsing immer in ein try/catch und gib eine richtige 400 zurück. Hier ist das Pattern, das ich in jedem Worker benutze:
async function readJson(request) {
try {
return { ok: true, data: await request.json() };
} catch (err) {
return {
ok: false,
error: 'Invalid JSON body',
detail: err.message,
};
}
}
export default {
async fetch(request) {
const result = await readJson(request);
if (!result.ok) {
return Response.json(result, { status: 400 });
}
const { email, plan } = result.data;
if (!email || !plan) {
return Response.json(
{ ok: false, error: 'email and plan are required' },
{ status: 422 },
);
}
return Response.json({ ok: true, email, plan });
},
};CORS für JSON-APIs
Wenn dein Worker von einem Browser auf einer anderen Origin aufgerufen wird — der Normalfall —
brauchst du
CORS-Header.
Browser schicken vor dem eigentlichen Request einen OPTIONS-Preflight für alles,
was mehr ist als ein einfaches GET. Behandle beides an einer Stelle:
const CORS_HEADERS = {
'Access-Control-Allow-Origin': 'https://app.example.com',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
'Access-Control-Max-Age': '86400',
};
export default {
async fetch(request) {
if (request.method === 'OPTIONS') {
return new Response(null, { status: 204, headers: CORS_HEADERS });
}
const data = { ping: 'pong', at: Date.now() };
return Response.json(data, { headers: CORS_HEADERS });
},
};Vermeide Access-Control-Allow-Origin: * bei allem, was Credentials liest oder
Nutzerdaten zurückgibt. Das ist einer dieser Shortcuts, die in Dev harmlos aussehen und in Prod zum
Security-Incident werden. Hardcode die Origins, die du tatsächlich bedienst, oder lies sie aus einer
Allowlist in env.
JSON an eine Upstream-API weiterleiten
Eine der häufigsten Nutzungen eines Workers ist als dünner Proxy: einen API-Key verstecken, eine Response umformen, Felder rauswerfen, die ein Client nicht braucht, oder zwei Upstream-Calls zu einem zusammenziehen. Hier ist ein Worker, der einen Upstream-Service aufruft, nur die Felder rausholt, die uns interessieren, und einen saubereren JSON-Payload zurückgibt:
export default {
async fetch(request, env) {
const url = new URL(request.url);
const userId = url.searchParams.get('id');
if (!userId) {
return Response.json({ error: 'id required' }, { status: 400 });
}
const upstream = await fetch(
`https://api.internal.example.com/users/${userId}`,
{
headers: { 'Authorization': `Bearer ${env.UPSTREAM_TOKEN}` },
},
);
if (!upstream.ok) {
return Response.json(
{ error: 'upstream failed', status: upstream.status },
{ status: 502 },
);
}
const full = await upstream.json();
// Strip internal fields before returning to the client
const safe = {
id: full.id,
displayName: full.display_name,
avatarUrl: full.avatar_url,
joinedAt: full.created_at,
};
return Response.json(safe);
},
};Zwei Dinge, auf die du achten solltest. Erstens: prüf immer upstream.ok,
bevor du .json() aufrufst — eine 500 vom Upstream enthält HTML oder eine Error-Seite,
und .json() darauf wirft genauso wie jedes andere malformed JSON.
Zweitens: halte Secrets wie UPSTREAM_TOKEN in Wrangler-Secrets
(wrangler secret put UPSTREAM_TOKEN) — nie in wrangler.toml
und nie in git committet.
JSON-Responses am Edge cachen
Wenn ein Upstream langsam oder teuer ist, lässt dich die Cache API JSON am Edge memoizen. Jedes Rechenzentrum hält seinen eigenen Cache, also zahlt der erste User in Frankfurt die Upstream-Kosten, und die nächsten 10.000 kriegen es in unter 5 ms aus dem RAM in der Nähe:
export default {
async fetch(request, env, ctx) {
const cache = caches.default;
const cacheKey = new Request(request.url, request);
let response = await cache.match(cacheKey);
if (response) {
return response;
}
const upstream = await fetch('https://api.example.com/popular-items');
const data = await upstream.json();
response = Response.json(data, {
headers: {
'Cache-Control': 'public, max-age=60',
},
});
// Don't block the response on the cache write
ctx.waitUntil(cache.put(cacheKey, response.clone()));
return response;
},
};Das ctx.waitUntil() ist der nicht offensichtliche Teil. Ohne das wird
cache.put() awaited, und deine Response wartet auf einen Disk-Write, der sie nicht
interessieren muss. Mit waitUntil kannst du die Response sofort zurückgeben,
während die Runtime den Cache-Write im Hintergrund am Leben hält. Das gleiche Pattern,
das du für Analytics-Beacons, Log-Forwarding, alles Fire-and-Forget benutzt.
Lokale Entwicklung mit Wrangler
Für Iterationen brauchst du keinen Cloudflare-Account. Installier Wrangler, bau ein Projektgerüst, und du hast eine lokale Workers-Runtime, die stark an die Produktion herankommt:
npm create cloudflare@latest my-json-api
cd my-json-api
npm run dev
# Worker is now live at http://localhost:8787
# Hit it from another terminal:
curl -X POST http://localhost:8787 \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","plan":"pro"}'Die lokale Runtime nutzt workerd, dieselbe Engine, die Cloudflare in Produktion fährt.
Die Verhaltensweisen, die unterschiedlich sind (KV-Latenz, Cache-Semantik, request.cf-Felder),
sind gut dokumentiert und beißen dich bei einfachen JSON-APIs selten. Deploy mit
wrangler deploy und derselbe Code ist in Sekunden weltweit live.
Nützliche Tools zum Bauen von Worker-JSON-APIs
Ein paar Tools, nach denen ich beim Bauen von Workern, die mit JSON arbeiten, ständig greife: JSON Formatter, um eine hässliche Upstream-Response schön auszugeben, die ich gerade reverse-engineere, JSON Validator, wenn ein POST-Payload scheitert und ich genau wissen muss, wo, JSON Path, um die Field-Picking-Logik zu planen, bevor ich sie schreibe, und JSON Minifier, wenn ich prüfen will, ob die Wire-Size für einen bestimmten Endpoint tatsächlich eine Rolle spielt.
Das JSON-Format selbst ist in
RFC 8259
spezifiziert — lesenswert, wenn du mal auf einen Edge Case wie "akzeptiert mein Parser NaN?"
stößt (Antwort: sollte er nicht). Cloudflares eigene
Workers Examples Gallery
hat Rezepte für JWT-Verifikation, A/B-Testing, HTML-Rewriting und ein Dutzend anderer Patterns,
sobald du die Basics aus diesem Artikel hinter dir hast.
Fazit
Cloudflare Workers passt sehr gut zu JSON-APIs — klein, schnell, global verteilt
und günstig genug, um Nebenprojekte laufen zu lassen. Der Happy Path ist nur
request.json() und Response.json(), aber der Produktionspfad
umfasst vier zusätzliche Gewohnheiten: Body-Parsing in try/catch wickeln, CORS-Header bewusst setzen,
upstream.ok prüfen, bevor du proxyte Responses parst, und
ctx.waitUntil für Cache-Writes und andere Hintergrundarbeit nutzen. Mach diese vier richtig
und du deployst Workers, die stehen bleiben.