REST API design - 14 - Caching : les bonnes réponses sont celles qu'on n'envoie pas

Cache-Control, ETag, requêtes conditionnelles 304, Last-Modified, CDN et invalidation : le caching HTTP de A a Z.

REST API design 15 / 25
  1. 01 REST API design - 00 - Pourquoi le design de ton API change tout
  2. 02 REST API design - 01 - Les principes REST que personne ne lit
  3. 03 REST API design - 02 - Des URLs qui ont du sens
  4. 04 REST API design - 03 - Les méthodes HTTP, pour de vrai
  5. 05 REST API design - 04 - Les codes de statut HTTP qu'il faut connaître
  6. 06 REST API design - 05 - Body, headers et le diable dans les détails
  7. 07 REST API design - 06 - La pagination, ou comment ne pas tuer ta base
  8. 08 REST API design - 07 - Filtrage et tri sans prise de tête
  9. 09 REST API design - 08 - La validation avec Zod, gardien de ton API
  10. 10 REST API design - 09 - Erreurs : un format que tes clients vont adorer
  11. 11 REST API design - 10 - Authentification : JWT, API keys et OAuth2
  12. 12 REST API design - 11 - Versioning : quand et comment faire évoluer ton API
  13. 13 REST API design - 12 - CORS : comprendre et debugger les erreurs cross-origin
  14. 14 REST API design - 13 - Rate limiting : protéger ton API sans frustrer tes clients
  15. 15 REST API design - 14 - Caching : les bonnes réponses sont celles qu'on n'envoie pas
  16. 16 REST API design - 15 - Upload de fichiers : multipart, signed URLs et streaming
  17. 17 REST API design - 16 - Relations entre ressources : embarquer, lier ou les deux
  18. 18 REST API design - 17 - HATEOAS : des liens dans tes réponses
  19. 19 REST API design - 18 - OpenAPI : le contrat entre ton API et le monde
  20. 20 REST API design - 19 - Documentation : une API non documentee est une API inutile
  21. 21 REST API design - 20 - Testing : tester ton API sans devenir fou
  22. 22 REST API design - 21 - Webhooks : quand c'est ton API qui appelle
  23. 23 REST API design - 22 - Performance : quand chaque milliseconde compte
  24. 24 REST API design - 23 - Sécurité : les attaques que tu vas subir (et comment t'en protéger)
  25. 25 REST API design - 24 - Glossaire : tous les termes REST API en un seul endroit

14 - Caching : les bonnes réponses sont celles qu'on n'envoie pas

Ce que tu vas apprendre

  • Les headers Cache-Control et leurs directives
  • Le mecanisme ETag et les requêtes conditionnelles (304 Not Modified)
  • La différence entre Last-Modified et ETag
  • Le caching CDN et les stratégies d'invalidation

Prerequisites

Avoir lu l'article sur le rate limiting. Comprendre le fonctionnement basique d'une requête HTTP (headers, status codes).


J'avais une API qui renvoyait la meme liste de categories 200 fois par minute. 200 requêtes, 200 réponses identiques, 200 fois la meme requête SQL. Le jour ou j'ai ajoute un Cache-Control: max-age=300, le nombre de requêtes qui atteignaient le serveur est passe a une toutes les 5 minutes. Meme réponse, 99.5% de bande passante en moins.

Les niveaux de cache

Le caching HTTP fonctionne a plusieurs niveaux :

  1. Cache navigateur : le browser stocke la réponse localement
  2. Cache proxy/CDN : un intermediaire (Cloudflare, Fastly) stocke la réponse
  3. Cache applicatif : ton serveur cache les donnees en mémoire (Redis, in-memory)

Les deux premiers sont contrôles par les headers HTTP. Le troisieme, c'est ton code. Cet article se concentre sur les headers.

Cache-Control

Le header Cache-Control est le chef d'orchestre. Il dit au client et aux intermediaires ce qu'ils peuvent cacher et pendant combien de temps.

Cache-Control: public, max-age=3600

Les directives principales :

  • public : tout le monde peut cacher (navigateur, CDN, proxys)
  • private : seul le navigateur peut cacher (donnees spécifiques a un user)
  • max-age=N : la réponse est valide pendant N secondes
  • no-cache : tu peux stocker, mais revalide a chaque fois auprès du serveur
  • no-store : ne stocke rien, jamais (donnees sensibles)
  • s-maxage=N : comme max-age mais uniquement pour les caches partages (CDN)
typescript// Donnees publiques, cachees 1 heure
app.get("/categories", (req, res) => {
  res.set("Cache-Control", "public, max-age=3600");
  res.json(categories);
});

// Donnees privees, cache navigateur seulement
app.get("/users/me", (req, res) => {
  res.set("Cache-Control", "private, max-age=60");
  res.json(user);
});

// Donnees sensibles, jamais cachees
app.get("/users/me/billing", (req, res) => {
  res.set("Cache-Control", "no-store");
  res.json(billing);
});

ETag et requêtes conditionnelles

Le max-age a un problème : quand il expire, le client refait une requête complète, meme si la donnee n'a pas change. L'ETag resout ca.

Le serveur généré un identifiant unique pour chaque version de la ressource (souvent un hash du contenu) :

HTTP/1.1 200 OK
ETag: "a1b2c3d4"
Cache-Control: no-cache

{"name": "Burger", "price": 12}

A la prochaine requête, le client envoie l'ETag :

GET /products/42 HTTP/1.1
If-None-Match: "a1b2c3d4"

Si la ressource n'a pas change, le serveur répond 304 Not Modified sans body. Le client utilise sa copie locale. Zero bande passante gaspillee.

typescriptimport crypto from "crypto";

app.get("/products/:id", (req, res) => {
  const product = getProduct(req.params.id);
  const etag = crypto
    .createHash("md5")
    .update(JSON.stringify(product))
    .digest("hex");

  res.set("ETag", `"${etag}"`);
  res.set("Cache-Control", "no-cache");

  if (req.headers["if-none-match"] === `"${etag}"`) {
    return res.status(304).end();
  }

  res.json(product);
});

Last-Modified

Alternative a l'ETag basee sur la date de modification :

HTTP/1.1 200 OK
Last-Modified: Sat, 29 Mar 2026 10:00:00 GMT

Le client envoie :

GET /products/42 HTTP/1.1
If-Modified-Since: Sat, 29 Mar 2026 10:00:00 GMT

Si la ressource n'a pas change depuis cette date, le serveur renvoie un 304. C'est plus simple qu'un ETag mais moins precis : deux modifications dans la meme seconde ne seront pas detectees. J'utilise Last-Modified pour les ressources qui changent rarement (fichiers statiques) et ETag pour les donnees dynamiques.

CDN caching

Un CDN comme Cloudflare ou Fastly se place entre tes clients et ton serveur. Il cache les réponses marquees public et les sert depuis ses edge servers, proches geographiquement du client. Ton serveur ne recoit meme pas la requête.

Cache-Control: public, s-maxage=3600, max-age=60

Ici, le CDN cache pendant 1 heure (s-maxage), mais le navigateur ne cache que 60 secondes (max-age). Quand le cache navigateur expire, le client interroge le CDN (rapide) plutot que ton serveur (lent).

Pour les pages d'un site comme paltemps.fr, le CDN cache les réponses des endpoints publics et les sert en quelques millisecondes, quel que soit le pays du visiteur.

L'invalidation : le vrai problème

Phil Karlton a dit qu'il n'y a que deux choses difficiles en informatique : nommer les choses et invalider le cache. Il avait raison.

Quand une ressource change, comment dire au cache de se mettre à jour ?

Stratégie 1 : TTL court. Tu mets un max-age de 60 secondes. Le cache se rafraichit naturellement. Simple, mais tu acceptes 60 secondes de donnees obsolètes.

Stratégie 2 : Purge explicite. Tu appelles l'API du CDN pour supprimer une entree :

typescriptawait fetch("https://api.cloudflare.com/client/v4/zones/{zone}/purge_cache", {
  method: "POST",
  headers: { Authorization: "Bearer {token}" },
  body: JSON.stringify({ files: ["https://api.example.com/products/42"] })
});

Stratégie 3 : Cache-busting par version. Tu ajoutes un paramètre de version : /products/42?v=3. Chaque mise à jour incremente la version.

En pratique, je combine TTL court pour les listes et purge explicite pour les ressources individuelles quand la fraicheur est critique.

Vary : le cache qui sait distinguer

Le header Vary dit au cache que la réponse depend d'autres headers. Si ton API renvoie du JSON ou du XML selon Accept :

Vary: Accept, Accept-Encoding

Le cache stocke une version par combinaison de Accept et Accept-Encoding. Sans Vary, un client qui demande du XML pourrait recevoir la version JSON cachee pour un autre client.

Résumé

  • Cache-Control contrôle qui peut cacher et pendant combien de temps
  • ETag + If-None-Match evitent de renvoyer des donnees inchangees (304)
  • Last-Modified est plus simple mais moins precis qu'un ETag
  • Le CDN cache les réponses public pres de tes clients
  • L'invalidation est le vrai defi : combine TTL, purge et cache-busting selon le cas
  • Vary garantit que le cache distingue les réponses selon les headers de la requête

Article précédent : Rate limiting Article suivant : Upload de fichiers

Sources

Réservez un audit gratuit de 30 minutes. Je vous montre concrètement ce qu'on peut automatiser.