Before & After

Code speaks louder than prose. Each block below produces the same response — the difference is what you’re telling HTTP infrastructure.
1. Complex filter search
Section titled “1. Complex filter search”❌ Before: POST for reads
Section titled “❌ Before: POST for reads”POST /api/employees/search HTTP/1.1Host: api.example.comContent-Type: application/json
{ "department": "engineering", "skills": ["typescript", "rust"], "experience_min": 3, "location": "remote", "active": true}✅ After: QUERY
Section titled “✅ After: QUERY”QUERY /api/employees HTTP/1.1Host: api.example.comContent-Type: application/jsonAccept: application/json
{ "department": "engineering", "skills": ["typescript", "rust"], "experience_min": 3, "location": "remote", "active": true}Server response is identical. What changes is the semantic contract.
2. GraphQL queries
Section titled “2. GraphQL queries”❌ Before: POST for everything
Section titled “❌ Before: POST for everything”POST /graphql HTTP/1.1Host: api.example.comContent-Type: application/json
{ "query": "query GetUser($id: ID!) { user(id: $id) { name email avatar posts { title } } }", "variables": { "id": "usr_42" }}✅ After: QUERY for reads, POST for mutations
Section titled “✅ After: QUERY for reads, POST for mutations”QUERY /graphql HTTP/1.1Host: api.example.comContent-Type: application/jsonAccept: application/json
{ "query": "query GetUser($id: ID!) { user(id: $id) { name email avatar posts { title } } }", "variables": { "id": "usr_42" }}GraphQL is the biggest immediate beneficiary. Queries can finally be cached at the HTTP layer — no hacks needed.
3. Elasticsearch
Section titled “3. Elasticsearch”❌ Before: POST _search
Section titled “❌ Before: POST _search”POST /products/_search HTTP/1.1Host: elasticsearch.internal:9200Content-Type: application/json
{ "query": { "bool": { "must": [ { "match": { "category": "electronics" } }, { "range": { "price": { "gte": 100, "lte": 500 } } } ], "filter": [ { "term": { "in_stock": true } } ] } }, "sort": [{ "price": "asc" }], "size": 20}✅ After: QUERY
Section titled “✅ After: QUERY”QUERY /products/_search HTTP/1.1Host: elasticsearch.internal:9200Content-Type: application/json
{ "query": { "bool": { "must": [ { "match": { "category": "electronics" } }, { "range": { "price": { "gte": 100, "lte": 500 } } } ], "filter": [ { "term": { "in_stock": true } } ] } }, "sort": [{ "price": "asc" }], "size": 20}Think monitoring dashboards polling every 5s: identical QUERY responses can be served from cache.
4. Absurd URL → structured body
Section titled “4. Absurd URL → structured body”❌ Before: GET with enormous query string
Section titled “❌ Before: GET with enormous query string”GET /api/reports?start_date=2026-01-01&end_date=2026-06-30&metrics=revenue,churn,mrr,arr,ltv,cac&group_by=month&filters[region]=latam&filters[plan]=pro,enterprise&filters[team_size]=50-200&include=comparison_period&comparison_start=2025-01-01&comparison_end=2025-06-30&format=detailed¤cy=BRL HTTP/1.1Host: api.example.com✅ After: QUERY with clean body
Section titled “✅ After: QUERY with clean body”QUERY /api/reports HTTP/1.1Host: api.example.comContent-Type: application/jsonAccept: application/json
{ "period": { "start": "2026-01-01", "end": "2026-06-30" }, "metrics": ["revenue", "churn", "mrr", "arr", "ltv", "cac"], "group_by": "month", "filters": { "region": "latam", "plan": ["pro", "enterprise"], "team_size": "50-200" }, "comparison": { "start": "2025-01-01", "end": "2025-06-30" }, "format": "detailed", "currency": "BRL"}URLs don’t leak into access logs. Complex filters stay structured. No more
414 URI Too Longsurprises.
What actually changes
Section titled “What actually changes”| Aspect | POST (before) | QUERY (after) |
|---|---|---|
| Auto-retry on failure | ❌ Unsafe — may duplicate side effects | ✅ Idempotent — safe to retry |
| Native HTTP caching | ❌ Caches ignore POST | ✅ Cacheable by URI + body |
| Correct semantics | ❌ “Create something” used for “read something” | ✅ “I’m asking a question” |
| CORS preflight | Depends on Content-Type | Yes (QUERY is not safelisted) |
Accept-Query discovery |
— | ✅ Server advertises accepted formats |
Retry safety
Section titled “Retry safety”POST /search → Client doesn't know if server processed it. Won't retry.QUERY /search → Client retries automatically. Spec guarantees: no side effects.Caching
Section titled “Caching”POST /search → 2x origin hit. Always.QUERY /search → 2nd served from cache (if response carries freshness headers).What does NOT change
Section titled “What does NOT change”- Response format — JSON, XML, whatever. Response is the same.
- Status codes —
200,404,400,500work the same way. - Error handling — Same validation logic and error treatment.
- Authentication — Bearer tokens, API keys, cookies — all unchanged.
- Content negotiation —
Acceptheader works normally. - Request body — Same JSON, same schema, same validation.
The only wire-level difference is the first line: QUERY instead of POST or GET.
Next step
Section titled “Next step”Want to see running code in Node.js, Go, Python, and curl?