La première fois que j'ai livré une API JSON sur Cloudflare Workers, je l'ai déployée depuis mon portable à 23h et elle tournait dans plus de 300 centres de données avant que je termine mon thé. Pas de Dockerfile, pas de cluster Kubernetes, pas de drame de démarrage à froid. Un simple wrangler deploy et un bundle de 1,2 Ko. C'est cette expérience qui a fait de Workers mon choix par défaut pour les services JSON-in, JSON-out — webhooks, proxys, agrégateurs d'API, authentification en périphérie. Si 80 % de votre backend se résume à « parser du JSON, faire une chose, retourner du JSON », voici l'article que j'aurais aimé avoir quand j'ai commencé.

Un Cloudflare Worker est essentiellement une seule fonction JavaScript (ou TypeScript) qui s'exécute sur des isolats V8 à la périphérie de Cloudflare. Il reçoit une Request, renvoie une Response, et a accès à l'API Fetch standard. Si vous avez déjà utilisé fetch() dans un navigateur, vous connaissez déjà 90 % du runtime. Ce que vous ne connaissez pas, c'est le petit ensemble de patterns qui distinguent un Worker jouet d'un Worker que vous pouvez réellement faire tourner en production. C'est le sujet de cet article.

Votre premier endpoint JSON

Voici le plus petit endpoint JSON utile. Il retourne un unique objet avec un timestamp et un message. Sauvegardez-le comme src/index.ts dans un projet Wrangler :

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

Deux choses à remarquer. D'abord, Response.json() est une fonction utilitaire statique qui sérialise l'objet et définit Content-Type: application/json pour vous. Ne fabriquez pas votre propre new Response(JSON.stringify(x)) à moins d'avoir besoin d'un content-type personnalisé — vous finirez par oublier l'en-tête. Ensuite, request.cf.colo vous indique quel centre de données Cloudflare sert la requête. Une requête depuis Berlin affichera FRA, depuis Tokyo elle affichera NRT. Tout l'argument « edge » tient dans ce seul champ.

Parser un corps de requête JSON

Les endpoints POST doivent lire un corps. L'API Fetch vous donne request.json(), qui lit le flux du corps et le parse en un seul appel :

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

Ça a l'air propre, mais ce code a un bug que vous rencontrerez dans les 24 heures suivant sa livraison : si le client envoie un corps vide ou du JSON malformé, request.json() lève une SyntaxError, votre Worker plante, et Cloudflare retourne une 500 générique. Ce n'est pas la réponse que vous voulez présenter aux clients.

Gérer le JSON malformé — ne le laissez pas renvoyer 500

Enveloppez toujours le parsing du corps dans un try/catch et retournez un 400 correct. Voici le pattern que j'utilise dans chaque 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 });
  },
};
Astuce de débogage : quand un client affirme « votre API est cassée » mais que le Worker retourne un 400, neuf fois sur dix leur JSON est malformé — virgule finale, clé non entourée de guillemets, ou caractère BOM accidentel au début. Demandez-leur de coller leur payload dans le Validateur JSON et le problème apparaît généralement en moins d'une minute.

CORS pour les API JSON

Si votre Worker sera appelé depuis un navigateur sur une origine différente — ce qui est le cas normal — vous avez besoin d'en-têtes CORS. Les navigateurs envoient un preflight OPTIONS avant la vraie requête pour tout ce qui est plus qu'un GET simple. Gérez les deux au même endroit :

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

Évitez Access-Control-Allow-Origin: * sur tout ce qui lit des identifiants ou retourne des données utilisateur. C'est l'un de ces raccourcis qui semblent inoffensifs en dev et se transforment en incident de sécurité en prod. Codez en dur les origines que vous servez réellement, ou lisez-les depuis une allow-list dans env.

Transférer du JSON vers une API upstream

L'une des utilisations les plus courantes d'un Worker est en tant que proxy mince : cacher une clé d'API, remodeler une réponse, retirer des champs dont un client n'a pas besoin, ou combiner deux appels upstream en un. Voici un Worker qui appelle un service upstream, sélectionne uniquement les champs qui nous intéressent, et retourne un payload JSON plus propre :

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

Deux choses auxquelles faire attention. Premièrement, vérifiez toujours upstream.ok avant d'appeler .json() — une 500 de l'upstream aura du HTML ou une page d'erreur, et appeler .json() dessus lève une exception comme pour n'importe quel autre JSON malformé. Deuxièmement, gardez les secrets comme UPSTREAM_TOKEN dans les secrets Wrangler (wrangler secret put UPSTREAM_TOKEN) — jamais dans wrangler.toml et jamais committés dans git.

Mettre en cache les réponses JSON en périphérie

Quand un upstream est lent ou coûteux, la Cache API vous permet de mémoïser du JSON en périphérie. Chaque centre de données garde son propre cache, donc le premier utilisateur à Francfort paye le coût upstream, et les 10 000 suivants l'obtiennent en moins de 5 ms depuis la RAM voisine :

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

Le ctx.waitUntil() est la partie non évidente. Sans ça, le cache.put() est attendu et votre réponse attend une écriture disque dont elle ne devrait pas se soucier. waitUntil vous permet de retourner la réponse immédiatement pendant que le runtime maintient l'écriture du cache en vie en arrière-plan. C'est le même pattern que vous utiliseriez pour les balises d'analytics, le forwarding de logs, n'importe quoi en fire-and-forget.

Développement local avec Wrangler

Vous n'avez pas besoin d'un compte Cloudflare pour itérer. Installez Wrangler, échafaudez un projet, et vous obtenez un runtime Workers local qui correspond étroitement à la production :

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

Le runtime local utilise workerd, le même moteur que Cloudflare exécute en production. Les comportements qui diffèrent (latence KV, sémantique du cache, champs request.cf) sont bien documentés et vous mordent rarement pour de simples API JSON. Déployez avec wrangler deploy et le même code est en ligne globalement en quelques secondes.

Outils utiles pour construire des API JSON Worker

Quelques outils auxquels je recours constamment en construisant des Workers qui manipulent du JSON : JSON Formatter pour formater joliment une réponse upstream moche que j'essaye de rétro-ingénier, JSON Validator quand un payload POST échoue et que j'ai besoin de savoir exactement où, JSON Path pour planifier la logique de sélection de champs avant de l'écrire, et JSON Minifier quand je veux vérifier si la taille sur le fil compte réellement pour un endpoint donné.

Le format JSON lui-même est spécifié dans RFC 8259 — qui mérite un coup d'œil si vous rencontrez un cas limite du type « mon parseur autorise-t-il NaN ? » (réponse : il ne devrait pas). La propre galerie d'exemples Workers de Cloudflare contient des recettes pour la vérification JWT, les tests A/B, la réécriture HTML, et une douzaine d'autres patterns une fois que vous aurez dépassé les bases de cet article.

Pour conclure

Cloudflare Workers est très bien adapté aux API JSON — petit, rapide, globalement distribué, et suffisamment bon marché pour laisser tourner des side projects. Le chemin heureux est juste request.json() et Response.json(), mais le chemin production implique quatre habitudes supplémentaires : envelopper le parsing du corps dans try/catch, ajouter des en-têtes CORS intentionnellement, vérifier upstream.ok avant de parser les réponses proxyées, et utiliser ctx.waitUntil pour les écritures de cache et autre travail en arrière-plan. Faites bien ces quatre choses et vous livrerez des Workers qui restent debout.