Första gången jag shippade ett JSON-API på Cloudflare Workers deployade jag det från laptopen kl 23 och hade det igång i över 300 datacenter innan jag hunnit dricka ur teet. Ingen Dockerfile, inget Kubernetes-kluster, inget cold-start-drama. Ett enda wrangler deploy och en 1,2 KB bundle. Den upplevelsen är anledningen till att Workers blivit mitt förstaval för JSON-in, JSON-ut-tjänster — webhooks, proxyer, API-aggregatorer, edge-auth. Om 80% av din backend är "parsa JSON, gör något, returnera JSON", är det här artikeln jag hade önskat att jag haft när jag började.

En Cloudflare Worker är i grunden en enda JavaScript- (eller TypeScript-) funktion som kör på V8-isolater vid Cloudflares edge. Den tar emot en Request, returnerar en Response, och har tillgång till standard-Fetch-API:t. Har du använt fetch() i en webbläsare kan du redan 90% av runtime-miljön. Det du inte kan är den lilla uppsättningen mönster som skiljer en leksaks-Worker från en du faktiskt kan köra i produktion. Det är vad den här artikeln handlar om.

Din första JSON-endpoint

Här är den minsta användbara JSON-endpointen. Den returnerar ett objekt med en timestamp och ett meddelande. Spara den som src/index.ts i ett 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);
  },
};

Två saker att lägga märke till. Först: Response.json() är en statisk hjälpfunktion som serialiserar objektet och sätter Content-Type: application/json åt dig. Skriv inte ditt eget new Response(JSON.stringify(x)) om du inte behöver en custom content type — du kommer bara glömma headern förr eller senare. För det andra säger request.cf.colo vilket Cloudflare-datacenter som serverar requesten. En request från Berlin visar FRA, från Tokyo visar den NRT. Hela "edge"-pitchen i ett enda fält.

Parsa ett JSON-body

POST-endpoints måste läsa ett body. Fetch-API:t ger dig request.json(), som läser body-strömmen och parsar den i ett enda anrop:

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 den här koden har en bugg du kommer stöta på inom 24 timmar efter shipping: om klienten skickar ett tomt body eller trasig JSON kastar request.json() ett SyntaxError, din Worker kraschar, och Cloudflare returnerar en generisk 500. Det är inte svaret du vill visa kunder.

Hantera trasig JSON — låt den inte bli 500

Omslut alltid body-parsning i try/catch och returnera en ordentlig 400. Här är mönstret jag använder i varenda 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 hävdar "ert API är trasigt" men Workern visar en 400 är nio av tio gånger deras JSON trasig — efterföljande kommatecken, ociterad nyckel eller en oavsiktlig BOM-karaktär i början. Be dem klistra in sin payload i JSON Validator så dyker problemet oftast upp på under en minut.

CORS för JSON-API:er

Om din Worker kommer att anropas från en webbläsare på en annan origin — vilket är normalfallet — behöver du CORS-headers. Webbläsare skickar en OPTIONS-preflight före den riktiga requesten för allt utöver en enkel GET. Hantera båda på ett ställe:

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

Undvik Access-Control-Allow-Origin: * på något som läser credentials eller returnerar användardata. Det är en sådan genväg som ser harmlös ut i dev och blir en säkerhetsincident i prod. Hårdkoda de origins du faktiskt betjänar, eller läs in dem från en allow-list i env.

Vidarebefordra JSON till ett uppströms-API

En av de vanligaste användningarna av en Worker är som tunn proxy: dölj en API-nyckel, forma om ett svar, strippa fält en klient inte behöver, eller sy ihop två uppströms-anrop till ett. Här är en Worker som anropar en uppströms-tjänst, plockar ut bara de fält vi bryr oss om, och returnerar en renare 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);
  },
};

Två saker att vara uppmärksam på. Först, kolla alltid upstream.ok innan du anropar .json() — en 500 från uppströms kommer ha HTML eller en felsida, och att anropa .json() på den kastar på samma sätt som vilken annan trasig JSON som helst. För det andra, håll hemligheter som UPSTREAM_TOKEN i Wrangler secrets (wrangler secret put UPSTREAM_TOKEN) — aldrig i wrangler.toml och aldrig committat till git.

Cacha JSON-svar vid edge

När ett uppströms-API är långsamt eller dyrt låter Cache-API:t dig memoisera JSON vid edge. Varje datacenter har sin egen cache, så den första användaren i Frankfurt betalar uppströms-kostnaden, och de nästa 10 000 får det på under 5 ms från RAM nära dem:

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() är den icke-uppenbara delen. Utan den awaitas cache.put() och ditt svar väntar på en diskskrivning det egentligen inte behöver bry sig om. waitUntil låter dig returnera svaret omedelbart medan runtime:n håller cache-skrivningen vid liv i bakgrunden. Det är samma mönster du skulle använda för analytics-beacons, loggvidarebefordran, allt som är fire-and-forget.

Lokal utveckling med Wrangler

Du behöver inget Cloudflare-konto för att iterera. Installera Wrangler, scaffolda ett projekt, och du får en lokal Workers-runtime som matchar produktion nära:

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 lokala runtime:n använder workerd, samma motor Cloudflare kör i produktion. Beteenden som skiljer sig åt (KV-latens, cache-semantik, request.cf-fält) är väldokumenterade och biter dig sällan för enkla JSON-API:er. Deploya med wrangler deploy så är samma kod live globalt på sekunder.

Användbara verktyg för att bygga Worker-JSON-API:er

Några verktyg jag griper efter konstant när jag bygger Workers som hanterar JSON: JSON Formatter för att snygga till ett fult uppströms-svar jag försöker reverse-engineera, JSON Validator när en POST-payload failar och jag behöver veta exakt var, JSON Path för att planera fält-plockar-logiken innan jag skriver den, och JSON Minifier när jag vill kolla om wire-storleken faktiskt spelar roll för en viss endpoint.

Själva JSON-formatet är specificerat i RFC 8259 — värt en snabb genomläsning om du någonsin träffar på ett edge-case som "tillåter min parser NaN?" (svar: det borde den inte). Cloudflares egna Workers-exempelgalleri har recept för JWT-verifiering, A/B-testning, HTML-omskrivning, och ett dussin andra mönster när du växt ur grunderna i den här artikeln.

Avslutningsvis

Cloudflare Workers passar mycket bra för JSON-API:er — små, snabba, globalt distribuerade, och billiga nog att kunna lämna sidoprojekt igång. Happy path är bara request.json() och Response.json(), men produktionsvägen innebär fyra extra vanor: slå in body-parsning i try/catch, lägg till CORS-headers medvetet, kolla upstream.ok innan du parsar proxade svar, och använd ctx.waitUntil för cache-skrivningar och annat bakgrundsarbete. Får du de fyra rätt kommer du shippa Workers som håller sig uppe.