Første gang jeg shippet et JSON-API på Cloudflare Workers, deployet jeg det fra laptopen kl. 23 og hadde det kjørende i 300+ datasentre før jeg var ferdig med teen. Ingen Dockerfile, ingen Kubernetes-klynge, intet cold-start-drama. En enkelt wrangler deploy og en 1,2 KB bundle. Den erfaringen er grunnen til at Workers har blitt mitt standardvalg for JSON-inn, JSON-ut-tjenester — webhooks, proxyer, API-aggregatorer, edge-auth. Hvis 80% av backenden din er "parse JSON, gjør noe, returner JSON", er dette artikkelen jeg skulle ønske jeg hadde da jeg begynte.

En Cloudflare Worker er i bunn og grunn en enkelt JavaScript- (eller TypeScript-) funksjon som kjører på V8-isolater i Cloudflares edge. Den mottar en Request, returnerer en Response, og har tilgang til standard Fetch-API. Har du brukt fetch() i en nettleser, kan du allerede 90% av runtime-en. Det du ikke kan er det lille settet med mønstre som skiller en leke-Worker fra en du faktisk kan kjøre i produksjon. Det er det denne artikkelen handler om.

Ditt første JSON-endepunkt

Her er det minste nyttige JSON-endepunktet. Det returnerer ett objekt med et tidsstempel og en melding. Lagre det som src/index.ts i et Wrangler-prosjekt:

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 å legge merke til. For det første: Response.json() er en statisk hjelper som serialiserer objektet og setter Content-Type: application/json for deg. Ikke lag din egen new Response(JSON.stringify(x)) med mindre du trenger en custom content type — du kommer bare til å glemme headeren til slutt. For det andre: request.cf.colo forteller deg hvilket Cloudflare- datasenter som serverer requesten. En request fra Berlin viser FRA, fra Tokyo viser den NRT. Hele "edge"-pitchen i ett felt.

Parse en JSON-request-body

POST-endepunkter må lese en body. Fetch-API-et gir deg request.json(), som leser body-strømmen og parser den i ett kall:

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 ut, men denne koden har en bug du treffer innen 24 timer etter at du shipper den: hvis klienten sender en tom body eller ugyldig JSON, kaster request.json() en SyntaxError, Workeren din crasher, og Cloudflare returnerer en generisk 500. Det er ikke svaret du vil ha foran kunder.

Håndtere ugyldig JSON — ikke la den bli 500

Pakk alltid body-parsing inn i try/catch og returner en skikkelig 400. Her er mønsteret jeg bruker 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-tips: når en klient hevder "API-et deres er ødelagt", men Workeren viser en 400, er det ni av ti ganger deres JSON som er ugyldig — et etterfølgende komma, en usitert nøkkel eller et utilsiktet BOM-tegn i starten. Be dem lime inn payloaden i JSON Validator, så dukker problemet som regel opp på under et minutt.

CORS for JSON-API-er

Hvis Workeren din skal kalles fra en nettleser på en annen origin — som er normaltilfellet — trenger du CORS-headers. Nettlesere sender en OPTIONS-preflight før selve requesten for alt utover en enkel GET. Håndter begge på ett 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 });
  },
};

Unngå Access-Control-Allow-Origin: * på noe som leser credentials eller returnerer brukerdata. Det er en av de snarveiene som ser harmløs ut i dev og blir til en sikkerhetshendelse i prod. Hardkod de origins du faktisk betjener, eller les dem fra en allow-liste i env.

Videresende JSON til et oppstrøms-API

En av de mest vanlige bruksområdene for en Worker er som en tynn proxy: skjul en API-nøkkel, omform et svar, fjern felt en klient ikke trenger, eller sy sammen to oppstrøms-kall til ett. Her er en Worker som kaller en oppstrøms-tjeneste, plukker kun feltene vi bryr oss 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 å være oppmerksom på. For det første: sjekk alltid upstream.ok før du kaller .json() — en 500 fra oppstrøms vil ha HTML eller en feilside, og å kalle .json() på den kaster på samme måte som enhver annen ugyldig JSON. For det andre: hold hemmeligheter som UPSTREAM_TOKEN i Wrangler secrets (wrangler secret put UPSTREAM_TOKEN) — aldri i wrangler.toml og aldri committet til git.

Cache JSON-svar på edge

Når et oppstrøms-API er tregt eller dyrt, lar Cache-API-et deg memoisere JSON på edge. Hvert datasenter har sin egen cache, så den første brukeren i Frankfurt betaler oppstrøms-kostnaden, og de neste 10 000 får det på under 5 ms fra RAM i nærheten:

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-åpenbare delen. Uten den awaites cache.put(), og svaret ditt venter på en diskskriving det egentlig ikke trenger å bry seg om. waitUntil lar deg returnere svaret umiddelbart mens runtime-en holder cache-skrivingen i live i bakgrunnen. Det er samme mønster du ville brukt for analytics-beacons, logvideresending, alt som er fire-and-forget.

Lokal utvikling med Wrangler

Du trenger ikke en Cloudflare-konto for å iterere. Installer Wrangler, scaffold et prosjekt, og du får en lokal Workers-runtime som matcher produksjonen tett:

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-en bruker workerd, samme motor som Cloudflare kjører i produksjon. Atferdene som skiller seg (KV-latens, cache-semantikk, request.cf-felt) er godt dokumentert og biter deg sjelden for enkle JSON-API-er. Deploy med wrangler deploy, og samme kode er live globalt på sekunder.

Nyttige verktøy for å bygge Worker-JSON-API-er

Noen verktøy jeg griper etter konstant når jeg bygger Workers som håndterer JSON: JSON Formatter for å pretty-printe et stygt oppstrøms-svar jeg prøver å reverse-engineere, JSON Validator når en POST-payload feiler og jeg trenger å vite nøyaktig hvor, JSON Path for å planlegge felt-plukke-logikken før jeg skriver den, og JSON Minifier når jeg vil sjekke om wire-størrelsen faktisk betyr noe for et gitt endepunkt.

Selve JSON-formatet er spesifisert i RFC 8259 — verdt en skumlesning om du noen gang treffer en edge case som "tillater parseren min NaN?" (svar: det burde den ikke). Cloudflares eget Workers examples-galleri har oppskrifter for JWT-verifisering, A/B-testing, HTML-omskriving og et dusin andre mønstre når du har vokst ut av grunnprinsippene i denne artikkelen.

Oppsummering

Cloudflare Workers passer veldig godt for JSON-API-er — små, raske, globalt distribuerte, og billige nok til at du kan la sideprosjekter kjøre. Happy path er bare request.json() og Response.json(), men produksjonsveien innebærer fire ekstra vaner: pakk body-parsing inn i try/catch, legg til CORS-headers bevisst, sjekk upstream.ok før du parser proxyede svar, og bruk ctx.waitUntil for cache-skrivinger og annet bakgrunnsarbeid. Får du de fire riktig, vil du shippe Workers som holder seg oppe.