DEV Community

Cover image for HTTP QUERY Method Explained: The REST Fix for GET vs POST (RFC 10008)
Umesh Malik
Umesh Malik

Posted on • Originally published at umesh-malik.com

HTTP QUERY Method Explained: The REST Fix for GET vs POST (RFC 10008)

The HTTP QUERY method (RFC 10008, June 2026) is a new REST request method that is safe and idempotent like GET but carries the query in a request body like POST — so large, structured queries finally get HTTP-layer caching and automatic retries. It's the first general-purpose method HTTP has gained since PATCH in 2010, and it closes a design gap REST API developers have been working around for 25 years. If you've ever tunneled a read-only search through POST and silently given up caching and retry safety, this method exists because of you.

TL;DR

  • QUERY is a new HTTP request method that sends a query in the request body while guaranteeing the operation is safe and idempotent — no state change, freely retryable, and cacheable by proxies and CDNs.
  • It exists because GET can't reliably carry a body (the spec gives GET bodies "no defined semantics", and URLs cap out around 2,000–8,000 characters) while POST forfeits caching and automatic retries even for purely read-only queries.
  • The cache key for a QUERY request is the URL plus the request body, so two identical searches can be served from the edge without touching your origin — something POST-based search endpoints will never get.
  • It's already real: Node.js parses QUERY natively (21.7.2+/22+), Fastify supports it via addHttpMethod(), nginx landed basic support, ASP.NET Core recognizes it in .NET 11 previews, and OpenAPI 3.2 models it — but browsers' fetch() can't send it yet.
  • My take: design new server-to-server search/filter/report endpoints as QUERY-shaped now (single query document in the body, mandatory Content-Type), even if you expose them via POST today. The migration later becomes a method-name change.

What Is the HTTP QUERY Method?

The HTTP QUERY method is a safe, idempotent request method, defined in RFC 10008, that asks a resource to run a query described by the request body and return the result. The body — with a mandatory Content-Type — carries the query parameters, so the URL no longer has to. Unlike POST, the server promises the operation changes nothing; unlike GET, the query can be as large and structured as you want: JSON documents, GraphQL queries, SQL-ish filters, JSONPath expressions.

The RFC's own framing is precise: a QUERY request asks "the target resource to perform a query operation within the scope of that target resource." Two guarantees make everything else possible. First, safety: "the client does not request or expect any change to the state of the target resource." Second, idempotency: QUERY requests "can be retried or repeated when needed, for instance, after a connection failure."

That second sentence is the quiet superpower. A dropped connection mid-POST leaves your client guessing — did the server process it? A dropped connection mid-QUERY is a non-event: any client, library, or proxy may resend it without asking.

Diagram showing the 25-year gap in HTTP's method table: GET is safe and cacheable but has no body, POST has a body but is unsafe and uncacheable, and the new QUERY method provides both — safe, idempotent, cacheable, with a request body

Why HTTP Needed a New Method

Here's the design flaw we all normalized: HTTP made you choose between honest semantics and a request body.

GET is the semantically correct method for reads. Every cache, proxy, prefetcher, and crawler on Earth understands it. But RFC 9110 gives content in a GET request "no generally defined semantics" — intermediaries may drop it, some servers reject it, and browsers won't send it. So real query data had to squeeze into the URL, which breaks somewhere between 2,000 and 8,000 characters depending on the browser, proxy, and server in the chain.

POST takes any body you like. But POST is defined as potentially unsafe and non-idempotent, so every intermediary treats your read-only search like a payment submission: no shared caching, no automatic retry, no prefetching. Every GraphQL query — explicitly read-only by design — loses HTTP-level caching and retry safety the moment it ships as POST.

The industry's workarounds tell the story better than any spec rationale:

This isn't a new idea, which makes the 18-year journey instructive. WebDAV had a body-carrying SEARCH method back in RFC 5323 (2008), but it never escaped WebDAV's orbit. The IETF HTTP working group adopted draft-ietf-httpbis-safe-method-w-body in 2021, initially to generalize SEARCH, renamed it QUERY by draft-02 in 2022 to shed the WebDAV baggage, and shipped RFC 10008 after fourteen draft revisions. The author list explains the industry buy-in: Julian Reschke (greenbytes, co-editor of the core HTTP specs), James Snell (Cloudflare), and Mike Bishop (Akamai). When the two largest CDNs co-author a caching-focused method, they intend to implement it.

Timeline of the HTTP QUERY method's standardization: WebDAV SEARCH in RFC 5323 (2008), the workaround era of Elasticsearch GET-with-body and GraphQL-over-POST (2015–2020), the httpbis draft adoption (2021), the rename from SEARCH to QUERY (2022), and RFC 10008 as Proposed Standard in June 2026

How QUERY Works on the Wire

A QUERY request looks like a POST that tells the truth. Here's the RFC's canonical example — a form-encoded query against a contacts collection:

Beyond the basic exchange, RFC 10008 nails down four behaviors that make QUERY more than "POST, but pinky-promise it's safe":

1. Content-Type is not optional. Servers "MUST fail the request if the Content-Type request field is missing or is inconsistent with the request content." No content sniffing, no guessing — a sharp break from POST's anything-goes reality.

2. Discovery via Accept-Query. A server advertises QUERY support and its accepted formats with a response header using Structured Field syntax:

OPTIONS /contacts HTTP/1.1
Host: api.example.com

HTTP/1.1 204 No Content
Allow: GET, HEAD, OPTIONS, QUERY
Accept-Query: application/x-www-form-urlencoded, application/jsonpath
Enter fullscreen mode Exit fullscreen mode

One subtlety worth knowing: the Accept-Query value "applies to every URI on the server that shares the same path" — the URL's query component is ignored for this purpose.

3. Results can get their own URIs. A 2xx response may include a Location header naming a URI you can later GET to re-run the equivalent query, or a Content-Location naming a resource that holds this specific result snapshot. That turns an expensive ad-hoc query into a shareable, bookmarkable, cacheable resource — a genuinely RESTful touch POST never standardized for reads.

4. Redirects behave like you'd hope. A 301 or 308 means repeat the QUERY at the new target; 303 means fetch the result with a plain GET. Crucially, the legacy exception that lets clients degrade a redirected POST into GET "does not apply to QUERY requests" — your query body survives the redirect.

Caching: The Actual Killer Feature

The RFC is blunt about the mechanics: "The cache key for a QUERY request MUST incorporate the request content and related metadata." URL plus body, hashed, stored. Identical query, identical key, edge-served response.

Diagram of HTTP QUERY caching flow: a client sends QUERY with a body, the CDN computes a cache key from the URL plus the request body, forwards a miss to the origin, caches the 200 response, and serves the next identical query from the edge without contacting the origin

Caches are even allowed to normalize semantically insignificant differences — stripping content encoding, or applying format-aware knowledge (JSON key order, whitespace) — so trivially different bodies can still share a cache entry. If that makes you nervous, no-transform opts out.

Think about what this does for a search-heavy API. Today, your "cache" for POST /search is an application-level Redis layer you built, keyed by a hash you invented, invalidated by rules you maintain. QUERY moves that entire concern into infrastructure that already exists at every hop between the user and your origin. This is the same architectural instinct as moving analytics to the edge with Cloudflare Workers — push work to the layer that's already positioned to do it.

💡 Key insight: QUERY doesn't make anything possible that was impossible before. It makes the default correct — caching, retries, and semantics you previously had to hand-build now fall out of the method name.

QUERY vs GET vs POST

The one-table version, straight from the RFC's own comparison:

That security row deserves a sentence: because the query moves out of the URL, it stops leaking into access logs, browser history, referrer chains, and analytics pipelines. RFC 10008's Security Considerations calls this out as a real benefit for sensitive queries — with the matching warning that servers minting result URIs must not embed sensitive query parts back into them.

Where QUERY Works Today (July 2026)

The honest adoption picture, one month after publication:

Adoption map for the HTTP QUERY method as of July 2026: works now in Node.js 21.7.2+/22+, Fastify via addHttpMethod, nginx basic support, and OpenAPI 3.2; in progress in ASP.NET Core .NET 11 previews, Spring Framework PR 34993, Ruby on Rails proposal, and CDNs; not yet in browsers, curl's native verbs, Express, Django, FastAPI, and CORS safelisting

On the working side: Node.js parses QUERY natively since 21.7.2 and 22+, and Fastify exposes it with one line. nginx landed basic upstream support, Spring Framework has an active PR (#34993), Rails has a live core proposal, and ASP.NET Core recognizes it in .NET 11 previews.

OpenAPI 3.2 also models QUERY operations — which means generated clients will offer it before most hand-written servers accept it.

Here's a working Fastify endpoint today:

import Fastify from 'fastify';

const app = Fastify();
app.addHttpMethod('QUERY', { hasBody: true });

app.route({
  method: 'QUERY',
  url: '/products',
  handler: async (req, reply) => {
    const results = await search(req.body); // body IS the query
    reply.header('cache-control', 'public, max-age=60');
    return results;
  }
});
Enter fullscreen mode Exit fullscreen mode

And from any client that lets you set a custom method:

curl -X QUERY https://api.example.com/products \
  -H 'content-type: application/json' \
  -d '{ "category": "keyboards", "maxPrice": 200, "sort": "rating" }'
Enter fullscreen mode Exit fullscreen mode

The missing piece is the browser. fetch() can't send QUERY until the WHATWG Fetch spec adds it, and even then QUERY is not a CORS-safelisted method, so cross-origin requests will always preflight. In 2026, QUERY is a server-to-server and API-gateway story — which is exactly where search, reporting, and internal query traffic lives anyway. If you build backend-for-frontend layers in Node.js, that BFF-to-service hop is the perfect first deployment.

Common Mistakes to Avoid

The #1 mistake: treating QUERY as a POST alias
The method is a contract, not a vibe. If your QUERY handler writes an audit row, increments a rate counter that changes responses, or mutates anything a client could observe, you've broken the contract — and a cache or retrying proxy will eventually expose that bug in production, far from the code that caused it. Side effects that aren't client-observable (logging, metrics) are fine, exactly as with GET.

Four more, seen in the wild already:

  1. Omitting Content-Type. Legal-ish on POST, a mandatory failure on QUERY. Fail these requests loudly, as the RFC requires — don't sniff.
  2. Forgetting the CORS preflight. QUERY from browser-adjacent contexts will send OPTIONS first. Your gateway needs Access-Control-Allow-Methods: QUERY or every request dies preflight.
  3. Leaking query content into result URIs. If QUERY returns a Location for later GETs, that URL will be logged everywhere. Use opaque result IDs, not serialized query parameters.
  4. Assuming your WAF and proxies pass it through. Older intermediaries reject unknown methods. Test the full path — one method-allowlist in a corporate proxy can 405 your rollout.

Should You Adopt QUERY Now?

FAQ

The Bottom Line

REST API design has spent 25 years contorting reads into methods that either couldn't carry the query or couldn't admit it was a read. The HTTP QUERY method ends that trade-off with the least glamorous, most durable kind of fix: a verb that tells the infrastructure the truth, and infrastructure that rewards honesty with caching and retries you no longer have to build.

You don't need to rewrite anything this quarter. But every new search, filter, or reporting endpoint you design from today should be QUERY-shaped: one query document in the body, a mandatory content type, and a handler with zero side effects. Do that, and adopting the verb itself — when your framework, gateway, and CDN all catch up — becomes a one-line change instead of a migration project.

If you found this useful, read Node.js Backend for Frontend Developers next — the BFF layer is exactly where QUERY will land first — or see how FastAPI's app.frontend() rethinks another old default.

Sources


Written for umesh-malik.com — no-fluff technical writing on AI, Web Dev, and Engineering.


Originally published at umesh-malik.com

Keep reading on umesh-malik.com:

Top comments (0)