Le problème : des GET trop à l’étroit, des POST trop menteurs#

Prenons un endpoint classique, /contrats, qui expose une liste de contrats. Pour tout récupérer, GET /contrats suffit. Pour paginer, on ajoute des paramètres d’URL : GET /contrats?page=1&size=50. Jusque-là, tout va bien.

Le problème arrive dès que le filtrage devient réellement complexe : plusieurs critères imbriqués, une liste de centaines d’identifiants à rechercher, un tri multi-champs. Tout cela ne rentre plus proprement dans une URL — entre les limites de longueur imposées par les navigateurs, les proxys et les load balancers, et le risque de voir des données sensibles finir dans les journaux d’accès du serveur ou l’historique du navigateur.

La solution qu’on utilise tous depuis des années : créer un endpoint séparé, du genre POST /contrats/search, capable d’accepter un corps de requête généreux. Ça marche, mais ça déplace le problème plutôt que de le résoudre. La RFC 9110 est claire : GET ne doit pas transporter de corps de requête significatif. Et POST, de son côté, n’est ni sûr ni idempotent — un intermédiaire (cache, proxy, CDN, navigateur) doit donc supposer par défaut qu’il modifie l’état du serveur. Résultat : impossible de mettre la réponse en cache, impossible de rejouer la requête sans risque en cas de coupure réseau, et rien dans la sémantique HTTP n’indique qu’il s’agit en réalité d’une simple lecture.

Ce que dit la RFC 10008#

Publiée en juin 2026 par le groupe de travail HTTP de l’IETF (Julian Reschke, James M. Snell et Mike Bishop en sont les auteurs), la RFC 10008 introduit la méthode QUERY : l’équivalent d’un GET qui accepterait un corps de requête, tout en conservant les garanties qui font la valeur de GET.

Trois propriétés la définissent :

  • Sûre — le client n’attend jamais de changement d’état du serveur en réponse à cet appel.
  • Idempotente — appeler la même requête QUERY une ou cent fois produit le même résultat.
  • Cacheable — comme pour GET, un cache partagé (CDN, proxy, navigateur) peut stocker et réutiliser la réponse.

Concrètement, une requête QUERY doit préciser un en-tête Content-Type décrivant le format de la requête envoyée dans le corps — la RFC ne l’impose pas à JSON, elle laisse la porte ouverte à d’autres langages de requête (SQL, JSONPath, XSLT…). Le serveur peut à son tour exposer, via l’en-tête Accept-Query, la liste des formats qu’il sait traiter. Et pour tirer parti du cache, la réponse peut porter un en-tête Content-Location qui identifie la ressource résultat de façon stable, réutilisable par un cache ou un client.

Détail pratique à ne pas oublier : QUERY ne fait pas partie des méthodes « CORS-safelisted » (GET, HEAD, POST). Un appel cross-origin déclenchera donc un vrai preflight OPTIONS — à prévoir côté configuration CORS si votre frontend et votre API ne sont pas sur la même origine.

À quoi ça ressemble en pratique#

Côté navigateur, rien n’empêche d’utiliser la méthode dès aujourd’hui via fetch, en la déclarant comme une méthode personnalisée :

JavaScript
const reponse = await fetch("/contrats", {
  method: "QUERY", // methode personnalisee : aucun support natif dedie pour l'instant
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    filtre: { statut: "actif", type: "assurance" },
    tri: { champ: "dateEcheance", ordre: "asc" },
    pagination: { page: 1, taille: 50 },
  }),
});

const contrats = await reponse.json();

Ça fonctionne, mais sans le bénéfice qu’on est venu chercher : les navigateurs ne savent pas encore mettre cette réponse en cache automatiquement, faute de reconnaître QUERY comme méthode cacheable au niveau du réseau. Le gain de performance réel ne viendra qu’une fois que caches partagés, CDN et navigateurs sauront tous lire cette sémantique — c’est le chantier le plus critique, et il se joue surtout côté serveur.

Vu du protocole brut, une requête QUERY et sa réponse ressemblent à ceci :

Bash
QUERY /contrats HTTP/1.1
Host: api.exemple.fr
Content-Type: application/json

{"filtre":{"statut":"actif"},"tri":"dateEcheance"}

HTTP/1.1 200 OK
Content-Type: application/json
Content-Location: /contrats?q=9f3a21b7
Cache-Control: max-age=300

[ ... resultats ... ]

Où en est l’adoption ?#

Comme pour toute nouvelle méthode HTTP, la diffusion prend du temps — PATCH, ajoutée en 2010, a mis plusieurs années à devenir un réflexe. QUERY suit un chemin comparable :

  • Le crate Rust http a déjà fusionné le support de la méthode.
  • .NET 10 sait la gérer nativement côté serveur.
  • Axum, Quarkus et le client API Bruno suivent activement son intégration.
  • Spring Boot travaille sur le support via une pull request en cours.

Ce n’est pas une coïncidence si des outils comme GraphQL ou Elasticsearch tunnelisent depuis longtemps des lectures complexes à travers des corps de requête POST : QUERY standardise enfin un besoin que ces écosystèmes avaient déjà contourné à leur manière. La RFC le dit explicitement : QUERY s’ajoute à GET et POST, elle ne les remplace pas.

GET, POST, QUERY : le comparatif#

  • GET — sûre, idempotente, cacheable, sans corps de requête (RFC 9110). Cas d’usage : lecture simple, ressource par id, pagination légère.
  • POST — ni sûre, ni idempotente, ni cacheable, avec corps de requête. Cas d’usage : création, action, tout ce qui modifie l’état.
  • QUERY — sûre, idempotente, cacheable, avec corps de requête. Cas d’usage : lecture complexe, recherche, filtres imbriqués, rapports.

Faut-il migrer vos API dès maintenant ?#

Pas dans la précipitation. QUERY est une amélioration progressive, pas un remplacement à marche forcée :

  • Gardez votre POST /contrats/search existant en parallèle — certains proxys, WAF ou load balancers plus anciens rejettent encore les méthodes qu’ils ne reconnaissent pas.
  • Ajoutez QUERY comme route additionnelle pointant vers le même handler, plutôt que de réécrire l’existant.
  • Documentez le format attendu via Accept-Query ou une réponse à une requête OPTIONS, pour que les clients sachent quoi envoyer.
  • Ne promettez pas de gain de cache immédiat : il dépendra de l’adoption de QUERY par votre CDN, vos caches partagés et, à terme, les navigateurs eux-mêmes.
Vingt ans à vivre avec le problème, cinq ans de discussions à l’IETF pour le résoudre : la vitesse d’adoption sur le terrain suivra probablement un tempo tout aussi mesuré.

En bref#

  • QUERY comble un vrai manque : lire avec un corps de requête riche, sans perdre le cache ni l’idempotence.
  • Elle s’implémente déjà côté client via fetch, mais le vrai bénéfice de performance dépendra du support côté serveurs, caches et CDN.
  • Le support framework par framework avance (Rust, .NET, Axum, Quarkus, Spring Boot en cours) mais reste à surveiller avant de baser une architecture dessus.
  • Pensez au preflight CORS si votre API est appelée en cross-origin : QUERY n’est pas une méthode « safelisted ».