TL;DR
- HTTP got a new method: QUERY (RFC 10008, June 2026). It's a safe, idempotent, cacheable request that carries a body — "GET with a body."
- It fixes a decade-old pain: complex reads (search/filter/graph queries) that don't fit in a URL, without abusing
POST. - The subtle part is caching: the RFC requires the request body to be part of the cache key. Get it wrong and one client gets another client's response.
-
http-queryableis a small library that does this correctly for Express, Fastify, rawhttp, and the browser.
What is the QUERY method?
For decades HTTP forced a bad choice for "reads with a big input":
-
GET— safe, idempotent, cacheable, but no body. Cram everything into the URL and hit length limits. -
POST— has a body, but it's unsafe and non-idempotent. Caches and proxies won't treat it as a read, and you can't safely retry.
QUERY is the missing middle: a body like POST, but the semantics of GET — safe, idempotent, and cacheable.
QUERY /search HTTP/1.1
Content-Type: application/json
{ "q": "cats", "filters": { "color": "black" } }
The catch nobody talks about: caching
RFC 10008 §2.7 is explicit: the cache key must incorporate the request content.
Shared HTTP caches have always keyed on method + URL. With QUERY, many different bodies hit the same URL:
QUERY /search { "q": "cats" } -> cats
QUERY /search { "q": "dogs" } -> dogs
A method+URL cache would serve the cats response to the **dctness and security bug — the RFC's Security Considerationscalls it out.
Doing it right: conservative body normalization
You want two things in tension:
-
{"a":1,"b":2}and{ "b":2, "a":1 }mean the same thing → same key (a hit). -
{"q":"cats"}and{"q":"dogs"}differ → *different keys
The key insight is an asymmetry: a false miss is harmle** is a bug (wrong data served). So every normalization must be*provably meaning-preserving, and when in doubt you normalize *less.
Safe for JSON: whitespace, object key order, string escape forms. NOT safe: merging numeric literals (JSON.parse("9007199254740993") returns ...992 — a
silent collision), guessing on duplicate keys, or normalizing ctand.
Show me the code
import express from "express";
import { queryable, QueryCache } from "http-queryable/express";
const app = express();
app.use(queryable({ cache: new QueryCache() }));
app.query("/search", (req, res) => res.json(search(req.body)));
app.listen(3000);
curl -X QUERY /search -d '{"q":"cats"}' # MISS -> cats
curl -X QUERY /search -d '{ "q" : "cats" }' # HIT (same meaning)
curl -X QUERY /search -d '{"q":"dogs"}' # MISS -> dogs
That third line is the whole point: a different body gets the cr a stale hit.
It handles the rest too
Accept-Query negotiation, Content-Location "switch to GET" flow CORS-safelisted), an isomorphic client with safe auto-retry,and Fastify + raw http adapters over the same core.
Try it
Node >= 22 (where QUERY lands in http.METHODS).
npm install http-queryable
Repo + docs: https://github.com/hardik-goel/http-queryable — stars and edge-case bug reports welcome.