Por que essa decisão importa mais do que parece
Troca de arquitetura de API em produção é um dos trabalhos mais caros que existem em software. Você muda o contrato entre front-end e back-end, precisa migrar clientes existentes, atualizar documentação e — na maioria das vezes — fazer os dois sistemas coexistirem por meses durante a transição.
Escolher certo no início poupa meses de retrabalho. E a escolha certa não é uma questão de preferência — é uma questão de caso de uso.
REST: o padrão e por que ainda domina
REST (Representational State Transfer) organiza a API em torno de recursos. Cada recurso tem uma URL, e as operações são os verbos HTTP: GET, POST, PUT, PATCH, DELETE.
GET /users → lista usuários
GET /users/123 → busca usuário por ID
POST /users → cria usuário
PATCH /users/123 → atualiza parcialmente
DELETE /users/123 → remove
Por que REST ainda é a escolha padrão:
- Cacheable por natureza: GET requests são cacheadas por browsers, CDNs e proxies sem configuração extra
- Fácil de debugar: qualquer ferramenta (curl, Postman, insomnia) funciona out of the box
- Sem overhead de runtime: não há uma camada de query language a ser processada
- Modelo mental simples: qualquer desenvolvedor entende sem treinamento específico
Onde REST sofre:
O problema clássico de REST é over-fetching e under-fetching. Você busca um usuário e recebe 30 campos quando só precisava de 3. Ou precisa de dados de usuário + posts + comentários e faz 3 requests separados.
// 3 requests para montar uma tela de perfil
const user = await fetch('/users/123');
const posts = await fetch('/users/123/posts');
const comments = await fetch('/users/123/comments');Isso pode ser resolvido com endpoints dedicados (/users/123/profile-summary) mas isso começa a criar acoplamento entre back-end e front-end.
GraphQL: flexibilidade com trade-offs
GraphQL é uma query language para APIs. O cliente especifica exatamente quais dados quer, e o servidor retorna apenas isso.
query {
user(id: "123") {
name
email
posts(last: 5) {
title
publishedAt
}
}
}Um único request retorna exatamente o que foi pedido. Sem over-fetching. Sem under-fetching.
Onde GraphQL brilha:
- Múltiplos clientes com necessidades diferentes: app mobile quer menos dados do que o dashboard web
- Desenvolvimento paralelo de front e back-end: o schema é o contrato, o front-end não precisa esperar o back-end criar um endpoint novo
- Consultas complexas com relações: buscar dados relacionados sem waterfall de requests
Os trade-offs reais:
// N+1 problem em GraphQL sem DataLoader
// Para 100 posts, isso faz 101 queries no banco
users {
name
posts {
title
author { name } // 1 query por post
}
}GraphQL exige atenção extra com o N+1 problem (resolvido com DataLoader/batching), cache (não funciona com HTTP cache nativo da mesma forma que REST) e segurança (queries maliciosas podem derrubar o servidor sem um sistema de query cost).
Comparação direta
| Critério | REST | GraphQL |
|---|---|---|
| Curva de aprendizado | Baixa | Média |
| Cache nativo | ✅ HTTP cache | ❌ Requer configuração |
| Over/under-fetching | Comum | Resolvido por design |
| N+1 problem | Não se aplica | Requer solução explícita |
| Tooling | Maduro e universal | Bom, mas mais específico |
| Versionamento | Via URL (/v1, /v2) | Evolução do schema |
| Ideal para | APIs públicas, CRUD simples | BFFs, múltiplos clientes |
Quando escolher REST
- API pública ou consumida por terceiros: REST é mais fácil de documentar e consumir sem ferramentas específicas
- Operações simples de CRUD: criar, ler, atualizar, deletar sem relações complexas
- Time sem experiência com GraphQL: a curva de aprendizado de GraphQL tem um custo real
- Necessidade de cache HTTP nativo: CDNs e browsers cacheiam GET requests automaticamente
Quando escolher GraphQL
- BFF (Backend for Frontend): uma camada de API que serve múltiplos clientes (web, mobile, TV) com necessidades diferentes
- Produto com domínio complexo: muitas entidades relacionadas onde o cliente frequentemente precisa de dados de múltiplas entidades juntos
- Times de front-end autônomos: quando os times de front não querem depender do back-end para cada nova tela
- Subscriptions em tempo real: GraphQL tem suporte nativo para subscriptions via WebSocket
A terceira opção que muita gente ignora
tRPC é uma solução elegante para projetos full-stack com TypeScript nos dois lados. Sem schema, sem geração de código — o contrato de tipos é inferido diretamente das funções do back-end.
// server
const appRouter = router({
userById: procedure
.input(z.string())
.query(({ input }) => db.user.findUnique({ where: { id: input } })),
});
// client — com autocompletar e tipagem completa
const user = await trpc.userById.query("123");Se você tem Next.js + Node.js com TypeScript nos dois lados, tRPC elimina a necessidade de definir tipos manualmente entre front e back. A DX é excelente.
Nossa posição
Usamos REST para a maioria dos projetos — especialmente APIs consumidas por terceiros ou projetos com operações principalmente CRUD. GraphQL entra quando o produto tem um domínio complexo com múltiplos clientes, como um SaaS com dashboard web + app mobile + API pública. tRPC usamos em projetos full-stack com Next.js onde a autonomia do tipo no front-end é prioridade.
A escolha errada não é catastrófica — pode ser migrada. Mas é caro. Vale pensar bem antes.
Precisa de ajuda para definir a arquitetura de back-end do seu projeto? Fale com nosso time.
