All articles
ProgrammingWeb DevelopmentAPIBackendHTTP

Meet QUERY: The Missing HTTP Method

QUERY is a new HTTP method: the body of a POST with the safety and caching of a GET. It is the verb we have been faking with POST for every search endpoint. What it is and how it looks in code.

Muhammad Junaid4 min read

TL;DRQUERY is a new HTTP method: the body of a POST with the safety and caching of a GET. It's the verb we've been faking with POST for every search endpoint. Here's what it is and how it looks in code.


The problem, in one screenshot

You have a search endpoint. It reads data and changes nothing. Semantically that's a GET. But the query is too rich for a URL:

GET /contacts?filter=%7B%22country%22%3A%22NL%22%2C%22tags%22%3A%5B%22vip%22%5D%7D&sort=-createdAt&limit=50 HTTP/1.1

Ugly, fragile, and it blows past URL length limits fast. So you try a body on the GET:

GET /contacts HTTP/1.1
Content-Type: application/json

{ "country": "NL", "tags": ["vip"], "sort": "-createdAt", "limit": 50 }

…and the spec shrugs: a GET body has "no defined semantics." Some proxies strip it, some servers reject it. Don't rely on it.

So everyone reaches for POST:

POST /contacts/search HTTP/1.1
Content-Type: application/json

{ "country": "NL", "tags": ["vip"], "sort": "-createdAt", "limit": 50 }

It works — but you just told every cache, proxy, and gateway a lie: that this read-only search modifies state. Because that's what POST means.


What GET and POST actually promise

  • Safe → no state change. Nothing to clean up after.
  • Idempotent → call it once or ten times, same result. Retry-friendly.
GET   →  ✅ safe   ✅ idempotent   ❌ body ("undefined")
POST  →  ❌ safe   ❌ idempotent   ✅ body

We've been stuck picking "safe semantics, no body" or "a body, unsafe semantics." There was no verb for "I have a body AND I'm only reading."

That verb is now QUERY.


Meet QUERY

Defined by the IETF HTTP Working Group in The HTTP QUERY Method (draft-ietf-httpbis-safe-method-w-body, draft 14 / Nov 2025, Standards Track):

QUERY requests that the server process the enclosed content in a safe and idempotent manner.

PropertyGETPOSTQUERY
Request body❌ undefined
Safe
Idempotent
Cacheable

The body of a POST, with the guarantees of a GET.


On the wire

QUERY /contacts HTTP/1.1
Host: example.org
Content-Type: application/json

{ "country": "NL", "tags": ["vip"], "sort": "-createdAt", "limit": 50 }
HTTP/1.1 200 OK
Content-Type: application/json

[
  { "surname": "Smith", "givenname": "John", "email": "john@example.org" }
]

Rules that matter:

  • Content-Type is mandatory — servers must reject a QUERY with a missing/inconsistent type.
  • Body format is your choice — JSON, form-encoded, GraphQL, SQL, SPARQL, a custom DSL.
  • Safe + idempotent by contract — if it has side effects, you're using the wrong verb.

In code

curl

curl -X QUERY https://api.example.org/contacts \
  -H "Content-Type: application/json" \
  -d '{ "country": "NL", "tags": ["vip"], "limit": 50 }'

Client — fetch (Node / browser)

const res = await fetch("https://api.example.org/contacts", {
  method: "QUERY",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ country: "NL", tags: ["vip"], limit: 50 }),
})

const contacts = await res.json()

Server — Express

import express from "express"

const app = express()
app.use(express.json())

// Route the new verb like any other method
app.use((req, res, next) => {
  if (req.method === "QUERY" && req.path === "/contacts") {
    return searchContacts(req, res)
  }
  next()
})

async function searchContacts(req: express.Request, res: express.Response) {
  const { country, tags, limit } = req.body
  const rows = await db.contacts.find({ country, tags }, { limit })
  res.json(rows) // safe + idempotent: no writes in here
}

The real payoff: caching

With POST-based search, caching is off the table — a shared cache sees POST and assumes a mutation, so every identical search hits your origin.

QUERY is declared safe, so caches may store and reuse the response. The twist: the cache key now includes the request body.

POST  /contacts/search  {body}   →  cache key = method + URL           →  ❌ not cached
QUERY /contacts         {body}   →  cache key = method + URL + BODY     →  ✅ cacheable

Same-path, different-body = different cached entries. Servers can also hand clients a canonical cacheable URL via Content-Location. Result: expensive searches cached at the edge, no hand-rolled "hash-the-query-into-a-URL" hack.


Where it fits in 2026

The API world has fragmented — gRPC for service-to-service, GraphQL/tRPC for typed frontends, streaming + events for real-time, MCP for AI agents calling your endpoints.

QUERY is the reminder that REST isn't dead — HTTP is still evolving. Instead of routing around HTTP's limits with another framework, it fixes the limit at the protocol layer, so caches, proxies, and gateways downstream get the benefit for free.

Good protocol design in a nutshell: not a new feature, just naming a guarantee we'd been faking.


Should you use it yet?

Soon — not quite today.

  • ⚠️ Still an Internet-Draft, not a finalized RFC.
  • ⚠️ Client / server / proxy support is catching up.
  • ✅ Worth knowing now, and worth keeping search endpoints genuinely side-effect-free so migration is trivial.

Next time you put a read-only query in a POST body and feel faintly guilty — that instinct was right. There's finally a proper verb for it.


Sources: The HTTP QUERY Method — IETF draft-ietf-httpbis-safe-method-w-body · MDN — GET · MDN — POST

This article is also published on Medium.

Keep reading