Todos os artigos
54 artigos · atualizado semanalmente Veja nossas Ferramentas
Todos os artigos
Todos

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.

COVER · Todos

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
  • sort com field: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.

RD
Autor
Rafael Duarte
Desenvolvedor backend com passagem por fintech e SaaS B2B — trabalhou em times que escalaram APIs de zero a milhões de requisições. Carrega cicatrizes de produção suficientes para ter opiniões fortes sobre ferramentas, padrões e decisões de arquitetura. Não é acadêmico: leu a RFC do UUID quando precisou escolher entre v4 e v7 para uma tabela de alta escrita.
Ver perfil