Il était 2 h 14 du matin et la ligne de log était banale, du genre qui te gâche la nuit : un client mobile avait pris un timeout sur POST /api/jobs/search, avait conclu qu'il ne pouvait pas relancer un POST sans risque, et avait affiché un écran vide à l'utilisateur. Rien n'avait été écrit, rien n'avait été facturé, aucune donnée n'avait bougé. L'endpoint était une lecture. On avait juste fait croire à tous les logiciels entre le téléphone et notre base de données que ce n'en était peut-être pas une, et ils nous avaient tous crus.

routes/api.php
<?php

use App\Http\Controllers\Api\SearchProductsController;
use Illuminate\Support\Facades\Route;

Route::query('/products/search', SearchProductsController::class);

C'est ce bazar que Laravel 13.35 me permet enfin de nettoyer pour de bon. La version ajoute Route::query(), proposé par Joel Butcher dans la pull request #61797, qui déclare une route pour le verbe HTTP QUERY. L'IETF a publié ce verbe sous la forme de la RFC 10008 en juin 2026, en tant que Proposed Standard, et toute sa raison d'être tient en une phrase : une requête qui transporte un corps tout en restant sûre et idempotente. Ma position est simple : si tu construis un nouvel endpoint dont les filtres tiennent mal dans une URL, passe à QUERY maintenant. Pas quand l'écosystème aura rattrapé son retard. Maintenant.

Côté framework, l'essentiel était déjà en place, et c'est pour ça que le moment me paraît venu. Depuis la 13.19, tu pouvais envoyer des requêtes QUERY avec Http::query() et les tester grâce aux helpers query() et queryJson(). Ce qui manquait, c'était un routeur qui parle ce verbe nativement : il fallait l'écrire en toutes lettres avec Route::match(['QUERY'], ...), ce qui marche mais sent la rustine. Avec la 13.35, le verbe fait partie de la liste du routeur, donc Route::any() et Route::redirect() le prennent aussi en compte, et déclarer l'endpoint tient en une ligne honnête dans routes/api.php, comme tu le verras ci-dessous.

Je veux laisser à l'autre camp tout son poids, parce que son argument est sérieux et pas juste ronchon. Une bonne partie de l'infrastructure n'a jamais entendu parler de QUERY. Une config de load balancer écrite en 2019, un WAF avec une liste blanche de méthodes, une API gateway dont le menu déroulant s'arrête à PATCH : n'importe lequel peut transformer en silence ta belle recherche en 405. Les navigateurs sur une autre origine enverront un preflight, parce que QUERY ne fait pas partie de la liste sûre du CORS. Et côté cache, ce n'est pas le repas gratuit que t'offre GET, puisque la clé de cache doit inclure le corps de la requête, ce que ton CDN ne fera presque certainement pas tout seul. Si tu mets la route dans le groupe de middleware web, la vérification CSRF de Laravel la traite comme un POST, donc il te faut un token ou une entrée dans except. Ça fait une vraie liste de corvées.

Voilà pourquoi je finis quand même par choisir QUERY. Chaque élément de cette liste est un endroit où ta stack prenait déjà une décision sur ton trafic de recherche sans que tu le remarques. POST /search n'était jamais mis en cache de toute façon, jamais relancé de toute façon, et le preflight partait déjà dès que tu passais en content type JSON. Changer de verbe ne crée pas ces coûts : ça les rend visibles et, pour la première fois, corrigeables, parce que la méthode elle-même dit au proxy que la requête est une lecture. Découvrir que ta gateway jette les verbes inconnus un mardi après-midi en staging, c'est un cadeau comparé à l'apprendre par le pager.

Il y a aussi un gain plus discret qui me tient encore plus à cœur que le cache. La RFC fait remarquer que les URL finissent plus souvent dans les logs que les corps de requête. Nos filtres de recherche d'emploi incluent les prétentions salariales et, pour certains clients, l'employeur actuel du candidat. Je n'aimais pas trop les voir en query string dans les logs d'accès, et j'aimais encore moins les cacher dans un POST, parce que ça faisait passer une lecture pour une écriture aux yeux de chaque auditeur qui survolait nos routes. Avec QUERY, le contenu reste à peu près privé et l'intention reste lisible.

Rien de tout ça ne veut dire que GET est en danger. Un numéro de page et un mot-clé ont leur place dans une URL que tu peux coller dans Slack, mettre en favori et confier à n'importe quel cache sur terre. PHP a toujours été bon à ce jeu-là, et $_GET n'est pas près de disparaître. QUERY, c'est pour l'endpoint qui a dépassé la barre d'adresse : des fourchettes de prix imbriquées, des tableaux d'ID, des booléens typés qu'une query string aplatirait en chaîne "1". Garde une route de repli pour les clients qui ne savent pas encore envoyer ce verbe, et considère l'audit de ton infrastructure comme une partie de la fonctionnalité, pas comme un obstacle.

Du coup, je suis curieux de savoir où tu vas te heurter au mur en premier. Si tu mets une route QUERY devant ta stack de production cette semaine, quelle couche t'attends-tu à voir la rejeter : le load balancer, le WAF, le CDN, ou un SDK que ton équipe frontend jure être nickel ? Et si tu as déjà essayé, qu'est-ce que tu as dû changer pour que le corps soit correctement mis en cache ?