Mismo endpoint con GET, POST y el nuevo método HTTP QUERY (RFC 10008) en Express
Demo en Express del mismo endpoint servido con GET, POST y el nuevo
métodoQUERY, para ver de un vistazo qué aporta y por qué importa la caché.
QUERY acaba de pasar a Proposed Standard. La idea en una frase:
QUERY= la seguridad y la cacheabilidad deGET+ la capacidad de mandar un body dePOST.
Enlaces:
Imagina un buscador de productos con filtros: categorías, rango de precio, rating, orden…
GET con query stringGET /api/products/search?categories=laptop,phone&priceMin=800&priceMax=1500&minRating=4.5&sortBy=price&sortOrder=desc
No hay un límite único universal.
A nivel de estándar HTTP, no se define una longitud máxima estricta para una URI. Lo que sí recomienda RFC 9110 es que clientes y servidores soporten al menos 8000 octetos en URIs dentro de elementos del protocolo.
En la práctica:
| Contexto | Límite recomendable |
|---|---|
| Máxima compatibilidad | ≤ 2.000 caracteres |
| Apps modernas controladas | ≤ 8.000 bytes aprox. |
| Sitemap SEO | ≤ 2.048 caracteres por URL |
| Apache por defecto | 8190 bytes en la request line |
| Nginx por defecto | alrededor de 8K para la request line, según buffer |
Mi recomendación práctica:
POST con body, no GET con query paramsEspecialmente si estás metiendo filtros, JSON, tokens o estado de la app en la URL, mejor evitar URLs largas. Pueden fallar en proxies, servidores, WAFs, navegadores, analytics, emails o herramientas intermedias.
GET (query params)Tradicionalmente, si querías filtrar un recurso, usabas query params en una petición GET
(p. ej. /api/v1/users?role=admin&status=active&sort=desc). Funciona bien para filtros
simples, pero en cuanto necesitas consultas relacionales complejas, anidamiento
profundo o lógica avanzada, la URL se vuelve enorme, difícil de leer y a veces choca
contra los límites de caracteres del navegador o del servidor.
Otros problemas potenciales:
encodeURIComponent), lo que?roles[0]=admin&roles[1]=reporter vs ?roles=admin&roles=reporter vs?roles[]=admin&roles[]=reporter.Y entonces, ¿por qué no mandar simplemente un GET con body JSON? En teoría debería
funcionar: ningún RFC de HTTP prohíbe explícitamente un body en un GET, pero sí indican
que no debería hacerse. Como consecuencia, cada cliente, proxy y servidor lo trata de forma
distinta: algunos lo rechazan, otros descartan el body y otros lo interpretan.
Por eso, usar GET con body es mala idea: por ejemplo, usuarios detrás del firewall
corporativo o con otro navegador podrían no poder usar tu web. Es también la razón de que
no haya un RFC nuevo que diga que GET ahora admite body: rompería muchísimas
implementaciones existentes. Justo el hueco que viene a llenar QUERY.
POST con bodyPOST /api/products/search
Content-Type: application/json
{ "categories": ["laptop","phone"], "price": { "min": 800, "max": 1500 }, "minRating": 4.5 }
POST se define como método no seguro (puede cambiar estado),Llevábamos años haciendo “buscar con
POST” sabiendo que estaba mal. Por eso naceQUERY.
QUERY 🎉QUERY /api/products/search
Content-Type: application/json
{ "categories": ["laptop","phone"], "price": { "min": 800, "max": 1500 }, "minRating": 4.5 }
POST.GET. La clave de caché es el contenido (el body).No hace falta nada raro. En Node 26 el parser HTTP ya incluye QUERY:
require('http').METHODS.includes('QUERY') // true
Y como Express 5 genera un método por cada verbo de http.METHODS, te expone
app.query() directamente (aunque aún no esté documentado, ver issue #5615):
app.query('/api/products/search', (req, res) => {
const filtros = req.body // express.json() parsea el body igual que en POST
// ...filtrar y responder con Cache-Control
})
express.json()mira el Content-Type, no el método, así que parsea el body
deQUERYexactamente igual que el dePOST. Cero configuración extra.
query-demo/
├── server.js # Express: MISMO endpoint con GET, POST y QUERY
├── search.js # Filtro compartido + caché en memoria (TTL 30s y delay simulado)
├── data/products.js # Catálogo de ejemplo
├── public/index.html # UI en el navegador: 3 verbos + petición HTTP en crudo + cURL/fetch
│ # (tipografía Geist Sans/Mono/Pixel servida desde el paquete)
├── requests.http # Peticiones listas para la extensión REST Client de VS Code
└── demo.sh # Demo con curl
El endpoint /api/products/search está implementado tres veces con la misma
lógica de filtrado; lo único que cambia es de dónde salen los filtros y la
política de caché:
| Verbo | Filtros desde | Cache-Control |
Caché |
|---|---|---|---|
GET |
query string | public, max-age=30 |
✅ HIT/MISS |
POST |
body JSON | no-store |
❌ siempre golpea la BBDD |
QUERY |
body JSON | public, max-age=30 |
✅ HIT/MISS |
Para que la caché se note, search.js simula una BBDD lenta (300 ms) y cachea
en memoria con un TTL de 30 s (el mismo valor que el max-age=30 del header, así
el HIT/MISS es coherente: pasados 30 s la entrada caduca y vuelve a ser MISS). La
respuesta incluye headers visibles:
X-Cache: HIT | MISSServer-Timing: db;dur=<ms> (0 ms cuando viene de caché)X-Filter-Source: querystring | bodynpm install # o pnpm install
npm start # http://localhost:3000 (o npm run dev para --watch)
Y en otra terminal:
npm run demo # lanza demo.sh con curl
También puedes abrir http://localhost:3000 para la UI, o usar requests.http
con la extensión REST Client de VS Code.
# GET — filtros en la URL (cacheable)
curl -i "http://localhost:3000/api/products/search?categories=laptop&priceMax=1200&minRating=4.5"
# POST — body JSON, pero Cache-Control: no-store
curl -i -X POST http://localhost:3000/api/products/search \
-H 'Content-Type: application/json' \
-d '{"categories":["tablet"],"price":{"max":1200}}'
# QUERY — mismo body que POST, pero cacheable
curl -i -X QUERY http://localhost:3000/api/products/search \
-H 'Content-Type: application/json' \
-d '{"categories":["phone"],"price":{"min":500,"max":1300},"minRating":4.5}'
QUERY de forma nativa.curl: ✅ -X QUERY funciona perfecto.fetch): ⚠️ el soporte está llegando, no es universal. Por esotry/catch que avisa si tu navegadorGET/HEAD: el HIT que ves en la demo es la caché del servidor (X-Cache).QUERY por método + body.Es decir: la especificación ya está, y el lado servidor funciona hoy; el
ecosistema (navegadores, CDNs) está poniéndose al día. Buen momento para contarlo. 🙂
MIT