Første gang jeg shippede et JSON-API på Cloudflare Workers, deployede jeg det fra min laptop kl. 23 og havde det kørende i 300+ datacentre, før jeg var færdig med min te. Ingen Dockerfile, ingen Kubernetes-klynge, intet cold-start-drama. Et enkelt wrangler deploy og en 1,2 KB bundle. Den oplevelse er grunden til, at Workers er blevet mit førstevalg til JSON-ind, JSON-ud-tjenester — webhooks, proxyer, API-aggregatorer, edge-auth. Hvis 80% af din backend er "parse JSON, gør noget, returnér JSON", er det her den artikel, jeg ville ønske, jeg havde haft, da jeg startede.

En Cloudflare Worker er i bund og grund en enkelt JavaScript- (eller TypeScript-) funktion, der kører på V8-isolater ved Cloudflares edge. Den modtager en Request, returnerer en Response, og har adgang til standard Fetch-API'et. Har du brugt fetch() i en browser, kender du allerede 90% af runtime-miljøet. Det du ikke kender, er det lille sæt af mønstre, der adskiller en legetøjs-Worker fra en, du faktisk kan køre i produktion. Det er, hvad denne artikel handler om.

Dit første JSON-endpoint

Her er det mindste nyttige JSON-endpoint. Det returnerer et enkelt objekt med et timestamp og en besked. Gem det som src/index.ts i et Wrangler-projekt:

js
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);
  },
};

To ting at bemærke. For det første er Response.json() en statisk hjælper, der serialiserer objektet og sætter Content-Type: application/json for dig. Lav ikke dit eget new Response(JSON.stringify(x)), medmindre du har brug for en custom content type — du vil bare glemme headeren på et tidspunkt. For det andet fortæller request.cf.colo dig, hvilket Cloudflare- datacenter der serverer requesten. En request fra Berlin viser FRA, fra Tokyo viser den NRT. Hele "edge"-pitchen i ét felt.

Parse en JSON-request-body

POST-endpoints skal læse en body. Fetch-API'et giver dig request.json(), som læser body-strømmen og parser den i ét kald:

js
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,
    });
  },
};

Ser rent ud, men koden har en bug, du rammer inden for 24 timer efter du shipper den: hvis klienten sender en tom body eller malformet JSON, kaster request.json() en SyntaxError, din Worker crasher, og Cloudflare returnerer en generisk 500. Det er ikke det svar, du vil have foran kunder.

Håndtering af malformet JSON — lad den ikke blive en 500

Pak altid body-parsning ind i try/catch og returnér en ordentlig 400. Her er mønsteret, jeg bruger i hver Worker:

js
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 });
  },
};
Debug-tip: når en klient påstår "jeres API er i stykker", men Workeren viser en 400, er det ni ud af ti gange deres JSON, der er malformet — et efterfølgende komma, en uciteret nøgle eller et utilsigtet BOM-tegn i starten. Bed dem indsætte deres payload i JSON Validator, og problemet dukker som regel op på under et minut.

CORS for JSON-API'er

Hvis din Worker skal kaldes fra en browser på en anden origin — hvilket er normalsituationen — har du brug for CORS-headers. Browsere sender en OPTIONS-preflight før den rigtige request for alt ud over en simpel GET. Håndtér begge dele ét sted:

js
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 });
  },
};

Undgå Access-Control-Allow-Origin: * på noget, der læser credentials eller returnerer brugerdata. Det er en af de genveje, der ser harmløs ud i dev og bliver til en sikkerhedshændelse i prod. Hardkod de origins, du faktisk betjener, eller læs dem fra en allow-liste i env.

Videresend JSON til et upstream-API

En af de mest almindelige anvendelser af en Worker er som en tynd proxy: skjul en API-nøgle, omform et svar, strip felter en klient ikke har brug for, eller sy to upstream-kald sammen til ét. Her er en Worker, der kalder en upstream-tjeneste, plukker kun de felter vi bekymrer os om, og returnerer en renere JSON-payload:

js
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);
  },
};

To ting at være opmærksom på. For det første, tjek altid upstream.ok før du kalder .json() — en 500 fra upstream har HTML eller en fejlside, og at kalde .json() på den kaster på samme måde som enhver anden malformet JSON. For det andet, hold hemmeligheder som UPSTREAM_TOKEN i Wrangler secrets (wrangler secret put UPSTREAM_TOKEN) — aldrig i wrangler.toml og aldrig committet til git.

Cache JSON-svar ved edge

Når et upstream-API er langsomt eller dyrt, lader Cache-API'et dig memoisere JSON ved edge. Hvert datacenter har sin egen cache, så den første bruger i Frankfurt betaler upstream-omkostningen, og de næste 10.000 får det på under 5 ms fra RAM i nærheden:

js
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;
  },
};

ctx.waitUntil() er den ikke-oplagte del. Uden den awaites cache.put(), og dit svar venter på en diskskrivning, det ikke har brug for at bekymre sig om. waitUntil lader dig returnere svaret med det samme, mens runtime'en holder cache-skrivningen i live i baggrunden. Det er samme mønster, du ville bruge til analytics-beacons, logvidere­sendelse, alt der er fire-and-forget.

Lokal udvikling med Wrangler

Du har ikke brug for en Cloudflare-konto for at iterere. Installér Wrangler, scaffold et projekt, og du får en lokal Workers-runtime, der matcher produktionen tæt:

bash
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"}'

Den lokale runtime bruger workerd, samme motor Cloudflare kører i produktion. De adfærd der adskiller sig (KV-latens, cache-semantik, request.cf-felter) er veldokumenterede og bider dig sjældent for simple JSON-API'er. Deploy med wrangler deploy, og samme kode er live globalt på sekunder.

Nyttige værktøjer til at bygge Worker-JSON-API'er

Et par værktøjer jeg griber efter konstant, når jeg bygger Workers der arbejder med JSON: JSON Formatter til at pretty-printe et grimt upstream-svar jeg prøver at reverse-engineere, JSON Validator når en POST-payload fejler, og jeg skal vide præcis hvor, JSON Path til at planlægge felt-pluk-logikken før jeg skriver den, og JSON Minifier når jeg vil tjekke, om wire-størrelsen faktisk betyder noget for et givent endpoint.

Selve JSON-formatet er specificeret i RFC 8259 — værd at skimme, hvis du nogensinde rammer en edge case som "tillader min parser NaN?" (svar: det burde den ikke). Cloudflares eget Workers examples-galleri har opskrifter til JWT-verificering, A/B-testing, HTML-omskrivning og et dusin andre mønstre, når du er vokset fra grundprincipperne i denne artikel.

Afrunding

Cloudflare Workers passer meget godt til JSON-API'er — små, hurtige, globalt distribuerede, og billige nok til at du kan lade sideprojekter køre. Happy path er bare request.json() og Response.json(), men produktionsvejen indebærer fire ekstra vaner: pak body-parsning ind i try/catch, tilføj CORS-headers bevidst, tjek upstream.ok før du parser proxede svar, og brug ctx.waitUntil til cache-skrivninger og andet baggrundsarbejde. Får du de fire rigtigt, vil du shippe Workers, der bliver ved med at køre.