Sua API faz update em uma chamada de GET?
O verbo DELETE nunca nem apareceu no seu código?
Se sim, respira fundo que esse artigo é pra você. Vamos falar sobre algumas boas práticas para APIs RESTful. E como evitar esses "pecados mortais" que acabam confundindo quem consome sua API.

O objetivo de uma API RESTful é muito parecido com o de um Design Pattern:
oferecer uma solução reutilizável para problemas comuns.
Um Design Pattern existe para acelerar o desenvolvimento com base em abordagens já testadas e amplamente utilizadas. Da mesma forma, o REST traz uma padronização que ajuda a tornar as APIs mais consistentes, previsíveis e fáceis de entender.
Em outras palavras: quando seguimos o padrão REST, o consumo da API fica mais homogêneo, reduzindo o esforço e a curva de aprendizado dos desenvolvedores que vão utilizá-la.
HTTP do jeito certo
Quando falamos em APIs REST, não estamos apenas expondo dados na web, estamos estabelecendo um contrato de comunicação. Esse contrato precisa ser previsível: quem consome a API deve entender claramente o que esperar de cada endpoint.
É aqui que entram os métodos HTTP e os conceitos de Safe e Idempotente. Eles garantem que uma API siga padrões consistentes, facilite a integração e evite comportamentos inesperados.
Safe x Idempotente
- Safe: métodos somente de leitura, que não alteram o estado do servidor.
- Idempotente: métodos que, independentemente de quantas vezes sejam chamados com a mesma intenção, produzem o mesmo efeito no estado do servidor (a resposta pode variar, mas o resultado final não muda).
Tabela de métodos
| HTTP Method | Safe | Idempotente | Descrição |
|---|---|---|---|
| OPTIONS | ✅ | ✅ | Retorna verbos e opções do resource (ex.: CORS). |
| GET | ✅ | ✅ | Busca um resource. |
| HEAD | ✅ | ✅ | Busca apenas o header do resource. |
| PUT | ❌ | ✅ | Atualiza ou substitui um resource por completo. |
| POST | ❌ | ❌ | Cria um resource ou executa uma ação não idempotente. |
| DELETE | ❌ | ✅ | Remove um resource. |
| PATCH | ❌ | ❌ | Atualiza parcialmente um resource. |
GET
O método GET é Safe e Idempotente. Ele deve apenas consultar dados, nunca criar, atualizar ou remover recursos. O resultado do GET será sempre o mesmo para o mesmo conjunto de informações, sem alterar o estado do servidor.
PUT
O método PUT substitui completamente a representação de um resource. É idempotente, pois várias chamadas com a mesma payload terão o mesmo efeito.
Exemplo:
{
"nome": "Maria"
}
- Primeira chamada: altera o nome para "Maria" →
200 OK. - Segunda chamada (mesma payload): nada muda, mas a resposta continua sendo
200 OK.
Por alterar estado, não é Safe, mas permanece idempotente.
DELETE
O método DELETE é idempotente, mas não é Safe. Ele remove o resource identificado pela URI. A primeira chamada remove o recurso; chamadas subsequentes não alteram mais nada no servidor, mas continuam válidas (idempotência).
Status comuns
- 204 No Content: remoção concluída, sem payload.
- 202 Accepted: remoção aceita, mas processada de forma assíncrona (ex.: filas, jobs).
- 404 Not Found: o recurso já não existe (mesmo assim, continua idempotente).
- 410 Gone: o recurso existiu, mas foi removido permanentemente.
Boas práticas
- Defina claramente se a remoção é hard delete (definitiva) ou soft delete (marcação lógica, ex.:
isDeleted=true). - Documente efeitos em cascata: caso a remoção impacte recursos relacionados, deve estar explícito (ex.:
?cascade=true). - Considere o uso de controle de concorrência com
ETageIf-Matchpara evitar remoções inconsistentes. - Sempre exija autenticação e autorização para chamadas de
DELETE.
POST
O método POST é usado para criar resources. Cada chamada pode resultar em um novo recurso, por isso não é nem Safe nem Idempotente.
Normalmente retorna 201 Created com o header Location, indicando a URI do recurso criado. Exemplo:
Location: /usuarios/123
PATCH
O método PATCH realiza atualizações parciais. Por padrão não é idempotente, embora possa ser projetado para ser, dependendo da implementação.
Exemplo de atualização parcial:
[
{ "operation": "replace", "field": "email", "value": "[email protected]" }
]
Se regras de negócio diferentes produzirem resultados distintos em chamadas subsequentes (ex.: limite de tempo entre alterações), a operação não será idempotente.
IMPORTANTE: Recurso principal x efeitos colaterais
Os conceitos de Safe e Idempotente se aplicam ao resource principal da requisição, não ao sistema como um todo.
Por exemplo:
Um GET em /faturas/123 não deve alterar a fatura em si (o recurso principal).
Porém, esse mesmo GET pode incrementar um contador de acessos, gerar um log de auditoria ou até mesmo atualizar um sistema de cobrança por consumo da API (billing).
Esses efeitos colaterais são aceitáveis, contanto que o estado do recurso consultado continue inalterado. Ou seja: a fatura continua igual, mesmo que seu acesso tenha sido registrado em outro lugar.
Endpoints
Padrão de nomenclatura
Seguir um padrão consistente de nomenclatura facilita a compreensão e a manutenção da API. Algumas boas práticas:
- Pluralidade: utilize o plural para disponibilizar um recurso. Exemplo:
/usersem vez de/user. - Kebab-case: para nomes compostos, prefira o formato
kebab-case. Exemplo:/global-configuration. - Minúsculas: mantenha as URLs em minúsculo, evitando variações como
/Users. Prefira sempre/users.
Estrutura de recursos
A raiz de um recurso deve representar uma coleção. Exemplo:
GET /users→ retorna uma lista de usuários.GET /users/bruno→ retorna o usuário de identificador únicobruno(não precisa ser o ID do banco, pode ser outro campo único, comousernameou um slug).
Operações típicas de CRUD sobre usuários:
GET /users→ retorna lista de usuários.GET /users/bruno→ retorna o usuáriobruno.POST /users→ cria um novo usuário.PUT /users/bruno→ atualiza os dados do usuáriobruno.PATCH /users/bruno→ atualiza parcialmente o usuáriobruno.DELETE /users/bruno→ remove o usuáriobruno.
Relacionamentos filho
Recursos relacionados devem ser mapeados de forma hierárquica:
GET /users/bruno/claims→ retorna todas as claims do usuáriobruno.GET /users/bruno/claims/6→ retorna a claim de id6.POST /users/bruno/claims→ cria uma nova claim para o usuáriobruno.PUT /users/bruno/claims/6→ atualiza a claim6.PATCH /users/bruno/claims/6→ atualiza parcialmente a claim6.DELETE /users/bruno/claims/6→ remove a claim6.
Nem sempre os relacionamentos seguem hierarquia direta.
Por exemplo, um usuário pertence a um departamento, mas pode fazer sentido expor /departamentos e /usuarios como recursos independentes, cada um com sua própria URI.
Ações
Até aqui, os exemplos são casos clássicos de CRUD. No mundo real, no entanto, recursos também possuem comportamentos.
Exemplo: considere o endpoint /candidatos/{id}. Agora é necessário aprovar ou recusar um candidato. Dentro do CRUD tradicional, poderíamos:
- Alterar o campo
statusviaPUT /candidatos/{id}. - Usar
PATCH /candidatos/{id}para atualizar apenas o campostatus.
O problema é que essas operações não traduzem a intenção de negócio. Mudar o status pode significar muito mais do que atualizar um campo:
- Aprovar um candidato pode envolver notificação ao gestor.
- Corrigir a grafia de um nome pode gerar reemissão de crachá.
Ambas seriam "atualizações", mas com implicações completamente diferentes.
Clareza com ações específicas
Uma abordagem mais clara é expor ações como endpoints específicos. Isso evita que o backend precise interpretar intenções ocultas em atualizações genéricas.
Exemplos:
POST /candidatos/{id}/aprovar -> aprova o candidato
DELETE /candidatos/{id}/aprovar -> cancela a aprovação
POST /candidatos/{id}/reprovar -> reprova o candidato
DELETE /candidatos/{id}/reprovar -> cancela a reprovação
POST /candidatos/{id}/transferir -> transfere o candidato para outra vaga
DELETE /candidatos/{id}/transferir -> cancela a transferência
Nesse formato, a intenção do endpoint é explícita e facilmente compreensível, sem a necessidade de regras complexas no backend para interpretar o que "atualizar status" realmente significa.
Vale lembrar: expor ações não fere os princípios REST. Pelo contrário, torna a comunicação mais clara, aderente à linguagem ubíqua (DDD) e alinhada ao domínio do negócio.
Pesquisas
É comum ver times criando endpoints de pesquisa via POST, enviando filtros no corpo da requisição. O problema é que o POST não é idempotente (pode gerar efeitos colaterais) e não é Safe (não deveria ser usado apenas para leitura). Por isso, ele não é a escolha ideal para consultas.
O POST foi feito para criação de recursos (associação clássica: POST -> INSERT). Para consultas o verbo correto é o GET, utilizando parâmetros de filtro na URL.
O problema do POST em consultas
Além da semântica incorreta, usar POST para buscas traz outro impacto grave: o cache.
- Caches HTTP (servidor, navegador e intermediários) não armazenam por padrão resultados de
POST. - CDNs como Cloudflare, Azure Front Door ou Akamai são otimizadas para cachear
GET. - Se você usa
POSTpara consultas, cada chamada sempre chega ao backend, aumentando latência e custo (mesmo quando o resultado poderia ser servido do cache).
Isso pode inviabilizar estratégias de escalabilidade que dependem de cache distribuído — justamente um dos pontos fortes de arquiteturas modernas em nuvem.
Exemplos corretos
GET /users→ retorna uma lista de usuários.GET /users/{id}→ retorna um único usuário.
Filtrando
Use query parameters para refinar os resultados:
GET /users?status=active&older_than=30
Essas URLs são cacheáveis, o que permite que CDNs armazenem a resposta e melhorem a performance da API globalmente.
Alias para pesquisas complexas
Se a consulta exigir muitos parâmetros, use aliases para simplificar:
Complexo:
GET /users?status=active&lives_in=brazil&older_than=18&younger_than=35Melhor com alias:
GET /users/young-brazilians
Além de mais legível, facilita o cache em camadas intermediárias.
Ordenação
Permita ordenação via query string, usando + (ascendente) ou - (descendente):
GET /users?sort=firstname,-last_login
Exemplo:
firstname→ ascendente.last_login→ descendente.
Paginação
Implemente paginação para evitar sobrecarregar a API. Convencionalmente:
GET /users?limit=10&offset=20→ retorna 10 registros, a partir do 21º.
Essa abordagem mantém a consulta cacheável, diferente de um POST que sempre iria até o backend.
Utilizar ProblemDetails
A RFC 7807 define um formato padrão para respostas de erro em APIs HTTP, chamado Problem Details.
O objetivo é simples: fornecer uma forma consistente e legível de transportar informações sobre erros, sem precisar inventar formatos próprios para cada API.
A grande vantagem está na consistência: quando respostas de erro seguem um padrão, a API se torna mais previsível e fácil de consumir.
Trecho da especificação:
This specification defines simple JSON [RFC7159] and XML [W3C.REC-xml-20081126] document formats to suit this purpose. They are designed to be reused by HTTP APIs, which can identify distinct "problem types" specific to their needs.
Um exemplo de resposta no formato ProblemDetails em JSON:
{
"type": "https://httpstatuses.com/404",
"title": "Not Found",
"status": 404,
"detail": "O usuário solicitado não foi encontrado.",
"instance": "/users/123"
}
JSON
Para APIs expostas na internet, prefira JSON como formato de resposta. Embora o XML já tenha sido amplamente utilizado (especialmente em SOAP), ele é verboso, pesado e mais difícil de interpretar.
Atualmente, o JSON é o padrão. XML e SOAP é sistema legado e pano pra manga.
Propriedades null
Evite retornar propriedades null nas respostas JSON. Isso só aumenta o tamanho do payload sem agregar valor.
APIs modernas e bibliotecas de serialização/deserialização sabem lidar com a ausência de campos. Assim, em vez de:
{
"id": 123,
"nome": "Bruno",
"telefone": null
}
Prefira:
{
"id": 123,
"nome": "Bruno"
}
HATEOAS
Um dos princípios teóricos do REST é o HATEOAS (Hypermedia as the Engine of Application State). Na prática, a ideia é que a navegação na API seja guiada por links, de forma parecida com a web tradicional.
Exemplo simplificado:
{
"id": 123,
"nome": "Bruno",
"links": [
{ "rel": "self", "href": "/users/123" },
{ "rel": "claims", "href": "/users/123/claims" }
]
}
Embora faça sentido no mundo na teoria, no contexto de APIs REST não faz muito sentido. E como sei que sempre vai ter um engraçadinho para dizer: Mas lá onde trampo... Então: A exceção comprova a regra.
Conclusão
Neste post compartilhei um conjunto de boas práticas para o design de APIs RESTful, fruto de experiências acumuladas ao longo dos anos em diferentes projetos. Embora não exista uma RFC única ou guia oficial definitivo sobre o tema, seguir padrões consistentes torna qualquer API mais previsível, fácil de usar e de manter.
As práticas aqui descritas não são regras absolutas, mas recomendações que todo mundo usa.
Referências
Algumas fontes que também reforçam e complementam as boas práticas apresentadas:

