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.

Top comments (1)
This is a strong write-up because it focuses on the part that will actually bite teams in production: the cache key, not the new method name. QUERY looks deceptively simple until the same endpoint starts receiving semantically different request bodies and your CDN or gateway still thinks in method-plus-URL. The normalization point matters a lot too, because otherwise two equivalent bodies can fragment cache behavior or create hard-to-debug misses. I’d be curious whether you’ve tested this behind common API gateways yet, because support at the spec level and support in the real proxy chain are usually very different stories.