Saltar al contenido principal

HTTP QUERY: el método que tardó 2 décadas en llegar

· 11 min de lectura
Oscar Adrian Ortiz Bustos
Contando lecturas...

Introducción

Es bien sabido que existen muchas maneras de diseñar APIs REST. Cada equipo tiene su estilo, sus convenciones y sus preferencias. Pero hay un patrón que nos alcanza a todos: GET para leer, POST para crear. Y eso está bien, porque es simple y funciona. Pero, como todo en la vida, hay un punto donde la simplicidad se rompe. Y es que cuántas veces no nos hemos topado con la necesidad de hacer una consulta compleja, con filtros avanzados, paginación y ordenamiento, y nos hemos visto obligados a usar POST para algo que claramente es una operación de lectura. Hoy vamos a hablar de eso, y de cómo el IETF finalmente nos dio una solución oficial: el método HTTP QUERY.

WelcomeBanner

¿Cuántas veces has visto esto en alguna API?

POST /users/search

Y cuando alguien del equipo lo ve, la pregunta es inevitable: "¿Por qué POST si solo estamos consultando datos?". Tienes razón, se siente raro. Pero tampoco había mucho de dónde agarrarse, porque GET, técnicamente, no puede llevar un body útil.

Este dilema no es nuevo. Lleva años siendo un punto de fricción en el diseño de APIs. Y en junio de 2026, el IETF publicó la respuesta oficial: RFC 10008 — el método HTTP QUERY.

El problema que QUERY viene a resolver

Para entender por qué QUERY existe, primero hay que entender qué le falta a GET y qué le sobra a POST en este contexto.

¿Qué le falta a GET?

GET es perfecto para solicitar un recurso simple. Pero tiene un límite fundamental: la URL. Técnicamente puedes enviar un body en un GET, pero la especificación HTTP original dice que los intermediarios (proxies, CDNs, cachés) pueden ignorarlo. En la práctica, muchos lo hacen.

Además, las URLs tienen un límite de longitud que varía por cliente y servidor, generalmente entre 2,000 y 8,000 caracteres. Si tu búsqueda tiene filtros avanzados, rangos de fechas, lógica booleana y campos anidados, eso no cabe cómodamente en una query string.

Imagina tener que encodear esto en una URL:

{
"filters": {
"role": "admin",
"active": true,
"created_between": ["2026-01-01", "2026-06-24"],
"tags": ["node", "linux", "backend"]
},
"sort": ["-created_at", "name"],
"page": 1,
"limit": 20
}

Se vería de la siguiente manera en una URL:

http://api.example.com/users/search?filters[role]=admin&filters[active]=true&filters[created_between][0]=2026-01-01&filters[created_between][1]=2026-06-24&filters[tags][]=node&filters[tags][]=linux&filters[tags][]=backend&sort[]=-created_at&sort[]=name&page=1&limit=20

No es imposible, pero es ilegible, frágil y difícil de mantener.

Aunque les sorprendería la cantidad de apis de empresas reconocidas que me he topado que encodean filtros complejos en la URL, y luego se quejan de que los clientes no pueden hacer búsquedas avanzadas. La realidad es que GET no está diseñado para eso. Cof, Cof, Meraki, cof.

¿Qué le sobra a POST?

POST sí puede llevar un body sin restricciones. El problema está en su semántica: POST no es seguro ni idempotente.

  • No es seguro: se asume que puede modificar estado en el servidor.
  • No es idempotente: hacer la misma petición dos veces puede producir resultados diferentes (imagina crear dos registros duplicados).
  • No es cacheable: los proxies y CDNs no pueden cachear respuestas POST por defecto.

Cuando usamos POST /users/search para buscar, estamos mintiendo sobre la intención de la operación. Buscamos datos, no creamos nada. Pero estamos usando un método que dice lo contrario, y eso tiene consecuencias reales en caching, retry automático y el comportamiento de los intermediarios de red.

Vaya, algo como esto:

curl -X POST http://api.example.com/users/search \
-H "Content-Type: application/json" \
-d '{"filters":{"role":"admin"}}'

Últimamente me he encontrado con peticiones POST que realmente son una consulta de datos (Cof Cof, LaravelRest, Cof), no me voy a enfocar ni a detener en si es mejor o no, porque este no es el objetivo del post. Lo que sí digo, es que es sumamente confuso semánticamente hablando, y es un problema que el IETF decidió resolver con QUERY.

¿Qué es HTTP QUERY?

QUERY es un nuevo método HTTP definido en el RFC 10008, publicado en junio de 2026 por el IETF httpbis Working Group. Fue el resultado de años de discusión bajo el nombre draft-ietf-httpbis-safe-method-w-body. Al que no pude asistir, es que estaba enfermo, pero me hubiera encantado estar ahí. Aunque de todas formas no me habían invitado.

Su premisa es simple pero poderosa: un método que sea seguro e idempotente como GET, pero que pueda llevar un body como POST.

Eso es exactamente lo que faltaba.

GET vs QUERY: ¿cuál usar cuándo?

CaracterísticaGETQUERY
Seguro (no muta estado)
Idempotente
Cacheable
Puede llevar body
Apto para filtros complejos
Retry automático por intermediarios

GET sigue siendo el rey para acceder a un recurso por su identificador: GET /users/42, GET /products/laptop-pro. Nada cambia ahí.

QUERY entra cuando la consulta es demasiado compleja para la URL: filtros avanzados, paginación con criterios múltiples, selección de campos, queries tipo GraphQL. Cualquier cosa que necesite un payload estructurado para describir qué quieres buscar.

POST vs QUERY: la diferencia que más importa

CaracterísticaPOSTQUERY
Seguro (no muta estado)
Idempotente
Cacheable por defecto
Puede llevar body
Semántica de lectura
Retry automático seguro

Esta es la comparación más relevante para el diseño de APIs de búsqueda.

Con POST, si la conexión se cae a mitad de la petición, un cliente inteligente no puede reintentar automáticamente porque no sabe si el POST ya se ejecutó o no. Con QUERY, puede reintentar sin miedo porque sabe que la operación es idempotente y no tiene efectos secundarios.

Y el punto que quizá más impacto tiene a escala: las respuestas QUERY son cacheables. Un CDN o proxy puede guardar la respuesta de una QUERY y servirla directamente en la siguiente petición idéntica, sin llegar al servidor. Con POST, eso simplemente no es posible por diseño.

Un ejemplo real

Lo quería probar por mí mismo así que me hice el servidor más básico, vulnerable y sin autenticación del mundo para demostrar QUERY en acción. Aquí el código en cuestión:

import http from "node:http";

const users = [
{ id: 1, name: "Adrian", role: "admin", active: true, created_at: "2026-06-20" },
{ id: 2, name: "Ana", role: "user", active: true, created_at: "2026-06-18" },
{ id: 3, name: "Luis", role: "admin", active: false, created_at: "2026-06-10" },
];

function readBody(req) {
return new Promise((resolve, reject) => {
let body = "";

req.on("data", chunk => {
body += chunk;
});

req.on("end", () => {
resolve(body);
});

req.on("error", reject);
});
}

const server = http.createServer(async (req, res) => {
// Discovery básico: anunciar que este recurso acepta QUERY con JSON
if (req.method === "OPTIONS" && req.url === "/users/search") {
res.writeHead(204, {
"Allow": "OPTIONS, QUERY",
"Accept-Query": "application/json",
"Access-Control-Allow-Methods": "OPTIONS, QUERY",
"Access-Control-Allow-Headers": "Content-Type, Accept",
});
return res.end();
}

if (req.method === "QUERY" && req.url === "/users/search") {
const contentType = req.headers["content-type"];

if (!contentType?.startsWith("application/json")) {
res.writeHead(415, {
"Content-Type": "application/json",
"Accept-Query": "application/json",
});

return res.end(JSON.stringify({
error: "Unsupported Media Type",
message: "Este endpoint solo acepta Content-Type: application/json"
}));
}

try {
const body = await readBody(req);
const query = JSON.parse(body);

let result = [...users];

if (query.filters?.active !== undefined) {
result = result.filter(user => user.active === query.filters.active);
}

if (query.filters?.role) {
result = result.filter(user => user.role === query.filters.role);
}

if (query.sort?.includes("-created_at")) {
result.sort((a, b) => b.created_at.localeCompare(a.created_at));
}

const limit = query.limit ?? 20;
result = result.slice(0, limit);

res.writeHead(200, {
"Content-Type": "application/json",
"Cache-Control": "max-age=60",
"Accept-Query": "application/json",
});

return res.end(JSON.stringify({
data: result,
meta: {
limit,
count: result.length,
},
}));
} catch {
res.writeHead(400, {
"Content-Type": "application/json",
});

return res.end(JSON.stringify({
error: "Bad Request",
message: "Body JSON inválido",
}));
}
}

res.writeHead(405, {
"Content-Type": "application/json",
"Allow": "OPTIONS, QUERY",
});

res.end(JSON.stringify({
error: "Method Not Allowed",
}));
});

server.listen(3010, () => {
console.log("Server running on http://localhost:3010");
});

La petición

curl -X QUERY http://localhost:3010/users/search \
-H "Content-Type: application/json" \
-d '{
"filters": { "active": true, "role": "admin" },
"sort": ["-created_at"],
"limit": 10
}'

La respuesta

{
"data": [
{ "id": 1, "name": "Adrian", "role": "admin", "active": true, "created_at": "2026-06-20" }
],
"meta": {
"limit": 10,
"count": 1
}
}

Y la respuesta incluye:

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: max-age=60
Accept-Query: application/json

¿Ven el Cache-Control: max-age=60? Eso es lo que no podemos hacer con POST.

Discovery via OPTIONS

El estándar también define cómo los clientes pueden descubrir si un endpoint soporta QUERY antes de intentarlo:

curl -X OPTIONS http://localhost:3010/users/search -v
HTTP/1.1 204 No Content
Allow: OPTIONS, QUERY
Accept-Query: application/json

El header Accept-Query cumple dos funciones: anuncia que el recurso acepta QUERY, y especifica qué formatos acepta en el body. En este caso, solo JSON.

Si envías un Content-Type que el servidor no acepta, responde con 415 Unsupported Media Type y devuelve el Accept-Query para que sepas qué sí es válido.

Error 415 en acción

curl -X QUERY http://localhost:3010/users/search \
-H "Content-Type: text/plain" \
-d "role=admin"
{
"error": "Unsupported Media Type",
"message": "Este endpoint solo acepta Content-Type: application/json"
}

El caching: la ventaja que más se subestima

Cuando digo que QUERY es cacheable, no hablo de algo trivial. En una API con tráfico real, las mismas búsquedas se repiten constantemente. Un panel de admin que siempre filtra por role: admin y active: true está ejecutando la misma query una y otra vez.

Con POST, cada petición llega al servidor. Con QUERY, el CDN puede responder directamente desde su caché después de la primera petición, sin tocar el servidor.

La clave está en cómo se construye la cache key: URI + body + Content-Type. Los intermediarios que implementen QUERY normalizan el JSON del body (removiendo espacios, reordenando keys) para maximizar los cache hits.

Es la misma lógica que tiene GET, pero con la potencia expresiva de un body estructurado.

Estado actual: RFC 10008

El método QUERY fue aprobado como Proposed Standard por el IESG el 20 de noviembre de 2025, y publicado como RFC 10008 en junio de 2026. Esto lo convierte en el primer método HTTP nuevo estandarizado en más de dos décadas.

El ecosistema todavía está adoptándolo. Express ya tiene soporte. Fastify tiene una discusión abierta. Spring, Django y FastAPI lo tienen pendiente. Los CDNs grandes aún no anuncian soporte oficial de caching para QUERY, pero es cuestión de tiempo.

La buena noticia es que del lado del servidor, implementarlo es trivial: es un método HTTP como cualquier otro. Del lado del cliente, curl ya lo soporta con -X QUERY. Lo que viene será que los frameworks y las herramientas lo adopten nativamente.

Conclusión

Desde La Cueva del Neandertech creemos que GET y POST no van a ningún lado, ni deberían. Cada uno tiene su lugar y su semántica bien definida. Pero durante años, el espacio entre ambos — "necesito hacer una consulta compleja sin mutar estado" — no tenía respuesta limpia.

QUERY llena ese hueco. No es un reemplazo de nada, es la herramienta que faltaba para hacer lo que hacíamos mal con POST o de forma incómoda con GET.

Si diseñas APIs con endpoints de búsqueda o filtrado avanzado, QUERY va a cambiar cómo lo haces. Y lo mejor es que la implementación del lado del servidor es inmediata, como lo demostré con el ejemplo en Node.js: un método HTTP más en tu router, con las respuestas correctas y los headers adecuados.

El estándar ya está. El ecosistema lo está adoptando. Es buen momento para conocerlo.

Quiero conocer tu opinión: ¿ya conocías QUERY? ¿Lo implementarías en tus APIs? Déjame un comentario abajo y conversemos.

"Las APIs son contratos. Un contrato que dice POST cuando quiere decir QUERY es un contrato que miente."

Adrian Ortiz
Escrito por un humano