REST API: princípios, boas práticas e erros comuns
Design de recursos, versionamento, status codes corretos e paginação — o que toda API deveria ter antes de chegar ao primeiro cliente em produção.
Você leu a documentação, entendeu o que é REST, implementou os endpoints. Seis meses depois a API tem /getUser, /updateUserData, /deleteUserById, retorna 200 mesmo quando falha, e o frontend precisa fazer quatro requests para montar uma tela. Não é má-fé — é falta de decisões explícitas no começo.
Este post assume que você já sabe o que é uma API. Aqui o foco é design: como estruturar recursos, versionar sem quebrar clientes, usar status codes corretamente e evitar os erros que todo time comete na primeira API que vai pra produção.
Design de recursos: substantivos, não verbos
O erro mais comum em APIs REST novas é usar a URL como se fosse uma chamada de função:
GET /getUsers
POST /createUser
DELETE /deleteUser?id=42
REST não é RPC. A URL identifica um recurso, não uma ação. O método HTTP já carrega a semântica da ação:
GET /users → lista usuários
POST /users → cria usuário
GET /users/42 → retorna usuário 42
PATCH /users/42 → atualiza parcialmente usuário 42
DELETE /users/42 → remove usuário 42
Algumas regras práticas que salvam discussões desnecessárias em PR:
Plural consistente. Use /users, não /user. A inconsistência entre singular e plural em endpoints diferentes é a fonte de mais bugs de integração do que parece.
Hierarquia representa relacionamento real. /users/42/orders faz sentido quando um pedido sempre pertence a um usuário e você precisa listar os pedidos daquele usuário. Mas /users/42/orders/18/items/3/discounts já é deep demais — nesse caso, um endpoint de discounts com filtros é mais prático.
Ações que não são CRUD são a exceção. POST /orders/18/cancel é aceitável quando "cancelar" tem lógica de negócio específica e não é simplesmente setar status = cancelled. Use com moderação.
Versionamento: decida antes de lançar
Uma API sem versão é uma API que não pode evoluir sem quebrar clientes. A discussão de "como versionar" geralmente ocorre tarde demais — depois que o primeiro cliente está em produção.
As três estratégias mais usadas:
Por URL (mais comum):
/api/v1/users
/api/v2/users
Vantagem: óbvio, explícito, fácil de testar no browser. Desvantagem: a URL carrega informação que não é sobre o recurso em si.
Por header:
GET /api/users
Api-Version: 2
Mais "RESTful" na teoria, mais trabalhoso na prática — cache, testes e documentação ficam mais complexos.
Por query parameter:
GET /api/users?version=2
Raramente a melhor escolha para versões maiores. Aceitável para flags experimentais.
Minha preferência é versionamento por URL para v1/v2/v3 e headers para variações menores. Mas o que importa mais do que a estratégia é a consistência: escolha uma e mantenha em toda a API.
Sobre deprecation: nunca remova um endpoint sem período de aviso. O padrão razoável é Sunset: Sat, 01 Jan 2027 00:00:00 GMT no header de resposta durante pelo menos três meses antes de desligar.
Status codes: use o vocabulário correto
HTTP tem um vocabulário de ~70 status codes. Na prática, você não precisa de todos eles — mas os que você usa, precisa usar corretamente. Retornar 200 com {"error": "User not found"} no body é o equivalente a gritar "está tudo bem!" enquanto a casa pega fogo.
Os que toda API deveria usar:
| Status | Quando usar |
|---|---|
| 200 OK | Sucesso genérico (GET, PATCH com retorno) |
| 201 Created | Recurso criado (POST com sucesso) |
| 204 No Content | Sucesso sem corpo de resposta (DELETE) |
| 400 Bad Request | Payload inválido, validação falhou |
| 401 Unauthorized | Não autenticado |
| 403 Forbidden | Autenticado mas sem permissão |
| 404 Not Found | Recurso não existe |
| 409 Conflict | Conflito de estado (ex: e-mail já cadastrado) |
| 422 Unprocessable Entity | Payload bem-formado mas semanticamente inválido |
| 429 Too Many Requests | Rate limiting |
| 500 Internal Server Error | Erro inesperado no servidor |
A distinção entre 401 e 403 confunde muita gente: 401 significa "eu não sei quem você é", 403 significa "eu sei quem você é, mas você não pode fazer isso". São erros diferentes que exigem ações diferentes do cliente.
Para consultar a especificação completa de cada código enquanto está desenvolvendo, uso o HTTP Status Codes do Quick Tools — mais rápido do que abrir a MDN quando você está no meio de um PR.
Corpo de erro padronizado
Status code correto é necessário mas não suficiente. O corpo da resposta de erro precisa ser consistente para que o cliente possa tratar programaticamente:
{
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid request payload",
"details": [
{
"field": "email",
"message": "Must be a valid email address"
}
]
}
}
O campo code (string, não o HTTP status) permite que o cliente trate casos específicos sem fazer parsing de message. O campo details é opcional mas essencial para erros de validação — sem ele, o frontend não sabe qual campo está errado.
Nunca exponha stack trace em produção. Logue internamente, retorne um request_id no header ou no corpo para rastreabilidade.
Paginação: cursor vs offset
Retornar listas sem paginação é a decisão que parece inofensiva no dia zero e vira uma query de 30 segundos seis meses depois.
Offset/page é o mais fácil de implementar:
GET /users?page=3&per_page=20
Funciona bem para listas pequenas e quando o usuário precisa pular para a página N diretamente. O problema é que com dados que mudam frequentemente, você pode pular ou repetir registros entre pages conforme inserções e deleções acontecem.
Cursor-based é mais robusto para feeds e dados de alta escrita:
GET /users?cursor=eyJ1c2VyX2lkIjoxMDB9&limit=20
O cursor é um ponteiro opaco (geralmente um ID ou timestamp codificado em base64) que indica a posição na lista. Sem os problemas de offset, mas sem suporte a "pular para a página 5".
A resposta deve incluir metadados de paginação:
{
"data": [...],
"pagination": {
"next_cursor": "eyJ1c2VyX2lkIjoxMjB9",
"has_more": true,
"total": 847
}
}
total é opcional e caro em alguns bancos — omita se a query de COUNT for um gargalo.
Filtros e ordenação
Filtros como query parameters são o padrão:
GET /orders?status=pending&created_after=2026-01-01&sort=created_at:desc
Algumas convenções que facilitam o uso:
- Datas em ISO 8601 (
2026-01-15T10:00:00Z), nunca timestamps Unix na query string — difícil de ler e de testar manualmente - Múltiplos valores com o mesmo parâmetro repetido:
?tag=api&tag=resté mais portável que?tags=api,rest sortcomfield:directioné legível e extensível:?sort=name:asc,created_at:desc
Erros comuns que custam caro
Quebrar contrato sem versionamento. Remover um campo, mudar o tipo de uma propriedade de string para número, renomear um endpoint — qualquer dessas ações sem versão nova quebra silenciosamente clientes que dependem do contrato antigo.
Autenticação no payload ao invés do header. POST /users?api_key=abc123 aparece em logs, histórico de browser e URLs compartilhadas. Use Authorization: Bearer <token>.
POST quando deveria ser idempotente. Operações que podem ser repetidas sem efeito colateral (criar um recurso com ID determinístico, por exemplo) devem ser PUT, não POST. POST não é idempotente — cada chamada cria um novo recurso.
Retornar o recurso completo em toda operação. DELETE /users/42 não precisa retornar o objeto deletado. PATCH /users/42 pode retornar o recurso atualizado ou apenas 204 — mas escolha um padrão e mantenha.
Ignorar idempotency keys. Para operações financeiras ou qualquer side effect crítico, o cliente deve poder retentar com segurança. Aceitar um header Idempotency-Key e cachear a resposta por 24h resolve o problema de retentativas sem duplicar efeitos.
Perguntas frequentes
Qual a diferença entre REST e RESTful?
REST (Representational State Transfer) é um conjunto de princípios arquiteturais definidos por Roy Fielding na sua tese de doutorado em 2000. RESTful é o adjetivo para APIs que seguem esses princípios. Na prática, a maioria das "APIs REST" do mercado é RESTful no sentido coloquial — usam HTTP, recursos com URLs e JSON — mas não seguem à risca os princípios de Fielding como HATEOAS. Isso não é necessariamente um problema; é uma escolha de tradeoff.
REST ou GraphQL: quando usar cada um?
REST é a escolha certa para a maioria das APIs: simples, cacheável, fácil de versionar e de documentar. GraphQL faz sentido quando diferentes clientes (mobile, web, parceiros) precisam de subconjuntos muito diferentes dos mesmos dados e você quer evitar over-fetching — mas adiciona complexidade operacional: linguagem de query própria, necessidade de proteção contra queries abusivas (rate limiting por custo), e curva de aprendizado. A maioria das APIs não precisa de GraphQL. Se você está em dúvida, comece com REST.
Como evitar breaking changes em APIs públicas?
Additive changes (adicionar campos novos, novos endpoints) são geralmente safe — clientes que não conhecem o campo novo simplesmente ignoram. Breaking changes (remover campos, mudar tipos, renomear) exigem versão nova. A estratégia mais segura é manter a versão antiga funcionando por um período definido (mínimo 3-6 meses para APIs públicas), anunciar a deprecation via header Sunset e documentação, e só então desligar.
O que é idempotência e por que importa em APIs?
Um método é idempotente quando chamá-lo múltiplas vezes produz o mesmo resultado que chamá-lo uma vez. GET, PUT e DELETE são idempotentes por definição. POST não é — cada POST cria um novo recurso. Isso importa em cenários de retry: se o cliente manda uma request e não recebe resposta (timeout, queda de rede), ele pode retentar com segurança se a operação for idempotente. Para operações críticas com POST, use Idempotency-Key no header.
O que a maioria das APIs ignora até a primeira crise
Design de API é uma das poucas decisões técnicas que é difícil de reverter depois — ao contrário de refatorar código interno, mudar um contrato público tem custo externo. As decisões de nomenclatura de recurso, estratégia de versionamento e formato de erro que parecem pequenas no dia zero voltam como dívida técnica ou incidente de produção.
O checklist mínimo antes de publicar um endpoint novo: recurso em substantivo plural, método HTTP correto, status code semântico, corpo de erro com code e message, paginação se retorna lista, e documentação do contrato — mesmo que seja um comentário no PR.
API bem feita é aquela que o cliente consegue integrar sem precisar te chamar no Slack para perguntar o que status: 1 significa.
- 01 VPS, VPC e servidor dedicado: diferenças VPS, VPC e servidor dedicado aparecem juntos em toda comparação de hospedagem — mas significam coisas bem diferentes. Entenda onde o dinheiro vai e como decidir.
- 02 O que é DevOps além das ferramentas DevOps não é um pipeline nem um cargo. É responsabilidade compartilhada entre quem escreve código e quem coloca em produção — e por que a maioria dos times erra nisso.