Antes e Depois

Código fala mais que texto. Cada bloco abaixo mostra o mesmo resultado — a diferença é o que você comunica à infraestrutura HTTP.
1. Busca com filtros complexos
Seção intitulada “1. Busca com filtros complexos”❌ Antes: POST para leitura
Seção intitulada “❌ Antes: POST para leitura”# Você está MENTINDO para a infraestrutura HTTP.
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}✅ Depois: QUERY
Seção intitulada “✅ Depois: QUERY”# QUERY é safe + idempotent + cacheable.# Proxies podem cachear. Clients podem fazer retry automático.
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}A resposta do servidor é idêntica. O que muda é o contrato semântico.
2. GraphQL queries
Seção intitulada “2. GraphQL queries”❌ Antes: POST para toda operação
Seção intitulada “❌ Antes: POST para toda operação”# GraphQL usa POST para TUDO — queries e mutations.# Resultado: zero caching HTTP nativo para reads.
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" }}✅ Depois: QUERY para reads, POST para mutations
Seção intitulada “✅ Depois: QUERY para reads, POST para mutations”# Agora a infraestrutura HTTP sabe que isso é read-only.# Retry automático em falha de conexão.
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 é o maior beneficiário imediato. Queries finalmente podem ser cacheadas no nível HTTP sem hacks.
3. Elasticsearch
Seção intitulada “3. Elasticsearch”❌ Antes: POST _search
Seção intitulada “❌ Antes: POST _search”# Elasticsearch sempre usou POST para buscas.# Cada poll de dashboard = request completo ao origin.
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}✅ Depois: QUERY
Seção intitulada “✅ Depois: QUERY”# Mesmo DSL. Mesma resposta. Semântica correta.# Um retry depois de timeout é seguro por definição.
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}Imagine dashboards de monitoramento fazendo polling: cada QUERY idêntica pode ser servida do cache.
4. URL gigante → corpo estruturado
Seção intitulada “4. URL gigante → corpo estruturado”❌ Antes: GET com query string absurda
Seção intitulada “❌ Antes: GET com query string absurda”# Tudo no URL. Limites variam por proxy (2KB? 8KB?).# Logs expostos. Bookmarks quebrados. 414 surpresa em produção.
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✅ Depois: QUERY com corpo limpo
Seção intitulada “✅ Depois: QUERY com corpo limpo”# Query no corpo. URL limpa. Sem limites de tamanho.
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 não vazam em logs de acesso. Filtros complexos ficam estruturados. Acabou o terror do
414 URI Too Long.
O que muda na prática
Seção intitulada “O que muda na prática”| Aspecto | POST (antes) | QUERY (depois) |
|---|---|---|
| Retry automático em falha | ❌ Unsafe — pode duplicar side effects | ✅ Idempotent — retry seguro |
| Cache HTTP nativo | ❌ Caches ignoram POST | ✅ Cacheable por URI + body |
| Semântica correta | ❌ “Crie algo” usado para “leia algo” | ✅ “Estou perguntando” |
| Preflight CORS | Depende do Content-Type | Sim (QUERY não é safelisted) |
Accept-Query discovery |
— | ✅ Servidor anuncia formatos aceitos |
Retry safety
Seção intitulada “Retry safety”# Conexão caiu no meio da resposta?
POST /search → Client não sabe se o servidor processou. Não faz retry.QUERY /search → Client refaz automaticamente. Garantia de spec: sem side effects.Caching
Seção intitulada “Caching”# Duas chamadas idênticas em 10 segundos:
POST /search → 2x origin hit. Sempre.QUERY /search → 2ª servida do cache (se response tem freshness headers).O que NÃO muda
Seção intitulada “O que NÃO muda”- Response format — JSON, XML, o que for. A resposta é a mesma.
- Status codes —
200,404,400,500funcionam igual. - Error handling — Mesma lógica de validação e tratamento de erros.
- Authentication — Bearer tokens, API keys, cookies — tudo igual.
- Content negotiation —
Acceptheader funciona normalmente. - Corpo da requisição — Mesmo JSON, mesmo schema, mesma validação.
A única diferença no wire é a primeira linha: QUERY no lugar de POST ou GET.
Próximo passo
Seção intitulada “Próximo passo”Quer ver código rodando em Node.js, Go, Python e curl?