The HTTP QUERY method (RFC 10008) is here - and caching it correctly is harder than it looks
DEV Community

The HTTP QUERY method (RFC 10008) is here - and caching it correctly is harder than it looks

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-queryable is a small library that does this correctly for Express, Fastify, raw http, 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 dogs request - a correctness and security bug. The RFC's Security Considerations calls 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 harmless, but a false hit 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 content type.

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 correct response, not 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
  • 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.

Comments

No comments yet. Start the discussion.