Finally, a Proper HTTP Method for Search: Understanding RFC 10008 (QUERY)

For years, we’ve been abusing HTTP.

Not in a catastrophic way, but in a subtle, “everyone does it so it’s fine” kind of way. The biggest offender? Using POST for read-only operations just because GET wasn’t flexible enough.

RFC 10008 introduces a new HTTP method — QUERY — that finally fixes this mismatch. It gives us a clean, semantically correct way to perform read-only operations with a request body.

If you’ve ever built a search endpoint, analytics API, or filtering system, this is for you.

The Problem We’ve Been Ignoring

HTTP gives us two obvious tools:

  • GET → safe, cacheable, no request body (in practice)
  • POST → allows body, but implies side effects

Now imagine building a real-world API:

  • Complex filters
  • Nested conditions
  • JSON payloads
  • Possibly even SQL-like queries

You can squeeze that into a query string… but it gets ugly fast:

GET /orders?filter=status:paid,amount>1000,created_at>2025-01-01&sort=-created_at

Or worse:

GET /search?q=%7B%22filters%22%3A%5B...%5D%7D

So what do most teams do?

They switch to:

POST /orders/search

Even though:

  • It doesn’t create anything
  • It doesn’t change state
  • It’s fully safe to retry

This is where semantics break down.

Enter QUERY

QUERY is essentially:

“Like POST, but safe and idempotent like GET.”

That’s it. That’s the missing piece.

You can send a request body, but:

  • No side effects are expected
  • It can be retried safely
  • It can be cached (in principle)
  • It accurately represents a read operation

A Simple Comparison

Think of it like this:

  • GET → “Fetch this resource”
  • QUERY → “Evaluate this query and return the result”
  • POST → “Execute this action”

That distinction becomes very real in API design.

Example 1: From Hacky GET to Clean QUERY

Old approach (GET)

GET /products?category=running&price_min=50&price_max=200&sort=rating

Fine… until it’s not.

Better approach (QUERY)

QUERY /products HTTP/1.1
Host: api.example.com
Content-Type: application/json

{
  "category": "running",
  "price": { "min": 50, "max": 200 },
  "sort": "rating"
}

Same intent, but:

  • Easier to evolve
  • Easier to validate
  • No URL length concerns
  • No encoding nightmares

Example 2: Complex Filtering (Where QUERY Shines)

This is where QUERY really earns its existence.

QUERY /orders HTTP/1.1
Content-Type: application/json

{
  "filters": [
    { "field": "status", "op": "eq", "value": "paid" },
    { "field": "total", "op": "gt", "value": 1000 }
  ],
  "date_range": {
    "from": "2025-01-01",
    "to": "2025-03-31"
  },
  "sort": ["-created_at"],
  "limit": 50
}

Try putting that into a query string without making your future self miserable.

Example 3: Query Languages (SQL / JSONPath)

If your API supports expressive queries, QUERY is a perfect match.

SQL-style

QUERY /analytics HTTP/1.1
Content-Type: application/sql

SELECT country, COUNT(*) as users
FROM sessions
WHERE created_at > NOW() - INTERVAL '7 days'
GROUP BY country

JSONPath-style

QUERY /documents HTTP/1.1
Content-Type: application/jsonpath

$..items[?(@.price > 100 && @.inStock == true)]

These are clearly read-only operations. Using POST here was always a semantic compromise.

A Subtle but Powerful Feature

RFC 10008 also allows something interesting:

The server can return a Location header that represents the query.

HTTP/1.1 200 OK
Location: /queries/abc123
Content-Location: /results/xyz789

This enables patterns like:

  • Save a query once
  • Reuse it via GET
  • Cache results more effectively
  • Share query URLs

This is particularly useful for:

  • dashboards
  • saved reports
  • analytics queries

When You Should Use QUERY

Use QUERY when:

  • The operation is strictly read-only
  • The request body is necessary (complex filters, large payloads)
  • You care about correct HTTP semantics
  • You want safe retries and better cache compatibility

When You Should NOT Use It

Do not use QUERY when:

  • You are creating or modifying resources
  • The operation has side effects
  • A simple GET with query params is sufficient

QUERY is not a replacement for GET. It’s a complement.

The Real Impact

This RFC doesn’t introduce a new capability — we could already do all of this with POST.

What it does is more important:

It gives us a standard, semantically correct way to model a very common pattern.

And that matters for:

  • API clarity
  • client expectations
  • caching layers
  • tooling (OpenAPI already supports it)
  • long-term maintainability

In other words, this is one of those small HTTP changes that quietly fixes a decade-old design smell.

Leave a Reply

Your email address will not be published. Required fields are marked *