REST vs GraphQL vs tRPC vs gRPC em 2026: O Guia Definitivo pra Escolher sua Camada de API
Você tá começando um projeto novo. Abre um arquivo em branco. E a discussão começa na hora.
"Vamos de REST? Todo mundo conhece REST." Aí alguém do time menciona GraphQL, fala que usou no emprego anterior e foi "incrível." Um terceiro engenheiro murmura algo sobre tRPC ser o futuro. E o sênior de backend, de braços cruzados, insiste que gRPC é a única escolha séria pra microserviços.
Essa discussão rola em todo time, em todo projeto novo, e em 2026 a resposta ainda é "depende" — mas agora a gente tem muito mais dados pra tomar essa decisão.
Esse guia compara REST, GraphQL, tRPC e gRPC como eles realmente funcionam em produção hoje — não como apareciam num tutorial de 2020. A gente cobre arquitetura, performance, experiência de desenvolvimento, e os custos reais de que ninguém fala. No final, damos um framework de decisão pra você parar de discutir e começar a construir.
O Cenário Mudou
Se seu modelo mental dessas tecnologias tá preso em 2022, você tá trabalhando com premissas ultrapassadas:
O que mudou desde 2022:
REST:
→ OpenAPI 3.1 é agora universal (alinhado com JSON Schema)
→ Fetch API tá em todo lugar (Node, Deno, Bun, browsers)
→ HTMX trouxe REST de volta pro discurso frontend
GraphQL:
→ Federation v2 amadureceu (Apollo, Grafbase, WunderGraph)
→ Relay Compiler integra com React Server Components
→ Subscriptions continuam estranhas; maioria dos times usa SSE
tRPC:
→ v11 lançado: suporte nativo a React Server Components
→ TanStack Start + tRPC é o novo meta full-stack
→ Continua sendo só TypeScript (esse é o ponto)
gRPC:
→ gRPC-Web estabilizou; protocolo Connect ganhou adoção
→ Buf.build + ConnectRPC melhoraram a DX drasticamente
→ Protocol Buffers → TypeScript codegen agora sem dor
O ponto: nenhuma opção é universalmente melhor. Cada uma otimiza pra uma restrição diferente. O erro é escolher baseado em hype em vez dos seus requisitos reais.
Como Funcionam de Verdade (Revisão de 30 Segundos)
Vamos alinhar os fundamentos antes de comparar:
REST
Client: GET /api/users/123
Server: { "id": 123, "name": "Alice", "email": "[email protected]" }
Client: GET /api/users/123/orders?limit=5
Server: [{ "id": 1, "product": "Widget", "total": 29.99 }, ...]
Orientado a recursos. Uma URL por recurso. Verbos HTTP (GET, POST, PUT, DELETE) definem operações. O servidor decide que dados retornar.
GraphQL
query { user(id: 123) { name email orders(limit: 5) { product total } } }
Linguagem de consulta sobre HTTP. Endpoint único (/graphql). O cliente decide que dados buscar. O servidor resolve os campos através de um sistema de tipos.
tRPC
// Server (definição do router) export const appRouter = router({ user: router({ getById: publicProcedure .input(z.object({ id: z.number() })) .query(async ({ input }) => { return db.users.findUnique({ where: { id: input.id } }); }), }), }); // Client (chamada direta de função — sem codegen, sem fetch) const user = await trpc.user.getById.query({ id: 123 }); // ^? { id: number, name: string, email: string }
Type safety end-to-end através da inferência do TypeScript. Sem linguagem de definição de schema. Sem geração de código. O router é o contrato da API.
gRPC
// user.proto service UserService { rpc GetUser (GetUserRequest) returns (User); rpc ListOrders (ListOrdersRequest) returns (stream Order); } message User { int32 id = 1; string name = 2; string email = 3; }
Protocolo binário (Protocol Buffers) sobre HTTP/2. Schema-first com geração de código. Suporta streaming nativamente. Projetado pra comunicação service-to-service.
A Comparação Real: O Que Realmente Importa
Performance
O que ninguém te mostra — latência medida e tamanhos de payload pra mesma operação (buscar usuário + 5 pedidos):
Protocolo Payload (bytes) Serialização Latência (p50) Latência (p99)
────────────── ─────────────── ────────────── ───────────── ─────────────
REST (JSON) 1,247 ~0.3ms 12ms 45ms
GraphQL 834 ~0.5ms 15ms 55ms
tRPC (JSON) 1,180 ~0.2ms 11ms 40ms
gRPC (proto) 312 ~0.1ms 4ms 12ms
Notas:
- REST faz over-fetch (~30% campos sem uso nesse exemplo)
- GraphQL adiciona overhead de resolvers (resolução field-level)
- tRPC tem overhead quase zero vs REST puro
- gRPC ganha no tamanho do wire mas precisa de HTTP/2
- Tudo medido em Node.js 22, mesma máquina, mesmo DB
Insight chave: Pra chamadas browser→servidor, a diferença de performance entre REST, GraphQL e tRPC é desprezível. A latência de rede domina. gRPC só brilha em comunicação service-to-service onde você controla os dois lados e faz milhares de chamadas por segundo.
Type Safety
É aqui que as diferenças reais aparecem:
Protocolo Schema Source Tipos no Cliente Validação Runtime
─────────── ────────────────── ──────────────── ─────────────────
REST OpenAPI (opcional) Codegen necessário Manual
GraphQL SDL (obrigatório) Codegen necessário Validação schema
tRPC TypeScript em si Automático (infer) Zod built-in
gRPC Protobuf (obrigatório) Codegen necessário Validação proto
// REST: Você escreve o tipo na mão (torce pra tá certo) const res = await fetch('/api/users/123'); const user = await res.json() as User; // 🤷 confia em mim // GraphQL: Codegen do schema (um build step a mais) const { data } = useQuery(GET_USER); // tipado se rodou codegen // tRPC: Tipos fluem automaticamente (zero passos extras) const user = await trpc.user.getById.query({ id: 123 }); // ^? inferido do schema Zod do server + tipo de retorno // gRPC: Codegen do .proto (um build step a mais) const user = await client.getUser({ id: 123 }); // tipado do proto
Vantagem matadora do tRPC: Muda o nome de um campo no server → seu código cliente tem um squiggly vermelho na hora. Sem build step. Sem codegen. Sem aquela ansiedade de "será que regenerei os tipos?".
Desvantagem matadora do tRPC: Só funciona quando cliente e servidor são TypeScript no mesmo repo (ou compartilham um pacote).
Experiência de Desenvolvimento
Vamos ser honestos sobre como é o dia a dia com cada um:
REST:
✅ Todo mundo conhece (curva de aprendizado zero)
✅ Curl-friendly (fácil debugar)
✅ Ecossistema de ferramentas incrível
❌ Sem type safety automático
❌ Over-fetching / under-fetching por padrão
❌ Versionamento é uma bagunça (v1, v2, v3...)
❌ Problema N+1 de endpoints pra UIs complexas
GraphQL:
✅ Queries dirigidas pelo cliente (busca exatamente o que a UI precisa)
✅ Schema auto-documentado
✅ Ótimo pra dados complexos e aninhados
❌ Caching é difícil (tchau HTTP caching)
❌ Problema N+1 no nível dos resolvers
❌ Mutations parecem "coladas"
❌ Curva de aprendizado íngreme pro full stack
❌ Upload de arquivo é sofrido
tRPC:
✅ Type safety com zero overhead
✅ Sem linguagem de schema pra aprender
✅ DX de monorepo incrível
✅ Mutations se sentem naturais
❌ Só TypeScript (nos dois lados)
❌ Não serve pra APIs públicas
❌ Acoplamento forte entre cliente e servidor
❌ Ecossistema menor que REST/GraphQL
gRPC:
✅ Melhor performance bruta
✅ Streaming nativo (bidirecional)
✅ Excelente história de backwards compatibility
✅ Codegen multi-linguagem
❌ Não é browser-native (precisa de proxy / Connect)
❌ Protobuf é outra linguagem pra aprender
❌ Debugar é doloroso (protocolo binário)
❌ Curva de aprendizado íngreme
Caching
Aqui o REST tem uma vantagem estrutural enorme:
REST:
HTTP caching simplesmente funciona™
- CDN caching com headers Cache-Control
- Browser caching (ETags, conditional requests)
- Proxy caching (Varnish, Nginx)
- Cada URL = cache key única
GraphQL:
HTTP caching tá essencialmente quebrado
- POST pra endpoint único = sem URL-based caching
- Precisa de persisted queries pra caching com GET
- Precisa de camadas de caching especializadas (Apollo, Stellate)
- Invalidação de cache é complexa (cache normalizado)
tRPC:
HTTP caching funciona (GET pra queries)
- TanStack Query cuida do caching do cliente
- CDN-cacheable com headers certos
- Cache key = procedure path + input
gRPC:
Sem HTTP caching (protocolo binário)
- Precisa de infra de caching customizada
- Geralmente resolvido no nível do service mesh (Envoy, Istio)
- Cache por hash da mensagem de request
Se sua API serve conteúdo que se beneficia de CDN caching (dados públicos, recursos que mudam pouco), REST é difícil de bater.
O Problema N+1: Todo Mundo Tem, Todo Mundo Resolve Diferente
O problema N+1 é a armadilha de performance mais comum em todos os estilos de API. Vamos ver como cada um lida com isso:
REST N+1
O que o cliente precisa:
- Perfil do usuário
- 10 últimos pedidos do usuário
- Status de envio de cada pedido
Abordagem REST (ingênua):
GET /api/users/123 → 1 request
GET /api/users/123/orders → 1 request
GET /api/orders/1/shipping → 1 request
GET /api/orders/2/shipping → 1 request
... (mais 10) → 10 requests
Total: 12 HTTP requests 😱
Abordagem REST (inteligente):
GET /api/users/123?include=orders.shipping → 1 request
(ou um endpoint BFF que agrega os dados)
GraphQL N+1
# O cliente manda UM request (boa!) query { user(id: 123) { name orders(last: 10) { id shipping { status, eta } # ← Aqui dispara N+1 no nível do resolver } } }
// O problema no lado do servidor: const resolvers = { Order: { shipping: (order) => db.shipping.findByOrderId(order.id) // Chamado 10 vezes! Uma por pedido! } } // Solução: DataLoader const shippingLoader = new DataLoader( (orderIds) => db.shipping.findByOrderIds(orderIds) ); const resolvers = { Order: { shipping: (order) => shippingLoader.load(order.id) // Agrupado em UMA query só } }
tRPC N+1
// tRPC não tem esse problema por padrão // porque você controla a query inteira num procedure: const userWithOrders = await trpc.user.getWithOrders.query({ id: 123 }); // Server-side: uma query com JOINs ou batch loading // Você escreve a lógica de data fetching, você controla as queries
gRPC N+1
// gRPC resolve na fronteira do serviço: rpc GetUserWithOrders(GetUserRequest) returns (UserWithOrders); // Ou usando streaming: rpc StreamOrderUpdates(OrderRequest) returns (stream OrderUpdate);
Conclusão chave: GraphQL move o problema N+1 do cliente pro servidor. REST joga pro cliente. tRPC e gRPC evitam deixando você definir procedures/RPCs específicos.
Padrões de Arquitetura do Mundo Real
Padrão 1: App Full-Stack TypeScript (tRPC)
Ideal pra: Apps SaaS, dashboards, ferramentas internas
┌──────────────────────────────────────┐
│ Next.js / TanStack Start Frontend │
│ (React + TanStack Query) │
│ │ │
│ tRPC Client │
│ │ (inferência de tipos) │
│ ▼ │
│ tRPC Server (validação Zod) │
│ │ │
│ Database (Prisma / Drizzle) │
└──────────────────────────────────────┘
Por que funciona:
- Muda uma coluna no DB → tipos quebram na UI na hora
- Zero documentação de API necessária (TypeScript É a documentação)
- Zod valida inputs; Prisma valida outputs
- Um repo, uma linguagem, um sistema de tipos
Padrão 2: Plataforma de API Pública (REST + OpenAPI)
Ideal pra: Plataformas de dev, APIs públicas, apps multi-cliente
┌────────────┐ ┌────────────┐ ┌────────────┐
│ Web Client │ │ Mobile App │ │ Third Party│
└─────┬──────┘ └──────┬─────┘ └──────┬─────┘
│ │ │
└────────────┬────┘─────────────────┘
▼
┌──────────────┐
│ REST API │
│ (OpenAPI 3.1)│
│ + Swagger │
└──────┬───────┘
│
┌──────▼───────┐
│ Services │
└──────────────┘
Por que funciona:
- Qualquer linguagem/plataforma consegue consumir
- OpenAPI gera SDKs pra todas as linguagens
- HTTP caching + CDN = escala de graça
- Todo mundo entende REST
Padrão 3: Dashboard Pesado em Dados (GraphQL)
Ideal pra: Dashboards analíticos, CMS, painéis admin multi-entidade
┌────────────────────────────────────────┐
│ Admin Dashboard (React) │
│ │
│ ┌─────────┐ ┌──────────┐ ┌────────┐│
│ │ Usuários│ │ Analytics│ │ Content││
│ │ Painel │ │ Charts │ │ Editor ││
│ └────┬────┘ └────┬─────┘ └───┬────┘│
│ │ │ │ │
│ └─────── GraphQL ─────────┘ │
│ (uma query por view) │
└───────────────────┬────────────────────┘
▼
┌───────────────┐
│ GraphQL Server│
│ (Federation) │
├───────────────┤
│ Users Service │
│ Analytics DB │
│ CMS Service │
└───────────────┘
Por que funciona:
- Cada painel busca exatamente o que precisa
- Uma request por view (sem waterfall)
- Federation deixa cada time ser dono do seu schema
- Schema = documentação automática
Padrão 4: Backend de Microserviços (gRPC)
Ideal pra: Backends de alto throughput, serviços polyglot, sistemas real-time
┌──────────────┐
│ API Gateway │ (REST/GraphQL pra clientes externos)
└──────┬───────┘
│ gRPC (interno)
▼
┌──────────────┐ ┌──────────────┐
│ User Service │◄───►│ Order Service│
│ (Go) │ │ (Rust) │
└──────┬───────┘ └──────┬───────┘
│ │
│ gRPC │ gRPC
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Auth Service │ │ Payment Svc │
│ (Python) │ │ (Java) │
└──────────────┘ └──────────────┘
Por que funciona:
- Protocolo binário = 5-10x menos bandwidth
- Streaming pra updates em tempo real
- Proto schema = contrato entre linguagens
- Service mesh cuida de discovery + load balancing
A Abordagem Híbrida: Como Produção Realmente É
A verdade que ninguém coloca nos posts de "REST vs GraphQL": a maioria dos sistemas de produção usa mais de um.
A arquitetura SaaS típica de 2026:
Externo:
┌─────────────────┐
│ REST API Pública│ (pra integrações, webhooks, SDKs)
└────────┬────────┘
│
Interno:
┌────────▼────────┐
│ tRPC / GraphQL │ (pro seu próprio frontend)
└────────┬────────┘
│
Backend:
┌────────▼────────┐
│ gRPC / REST │ (service-to-service)
└─────────────────┘
O Framework de Decisão
Para de discutir. Segue esse flowchart:
START: Quem consome sua API?
├── Devs externos / API pública
│ └── REST + OpenAPI 3.1
│ (universal, cacheável, bem entendido)
│
├── Seu próprio frontend (monorepo TypeScript)
│ ├── Necessidades de dados simples?
│ │ └── tRPC
│ │ (zero overhead, máximo type safety)
│ └── Dados aninhados complexos / múltiplos clientes?
│ └── GraphQL
│ (queries flexíveis, dirigidas pelo cliente)
│
├── Service-to-service (microserviços internos)
│ ├── Precisa de streaming / alto throughput?
│ │ └── gRPC
│ │ (protocolo binário, streaming nativo)
│ └── CRUD simples entre poucos serviços?
│ └── REST
│ (mantenha simples)
│
└── Não tem certeza / prototipando?
└── Comece com REST
(sempre pode migrar depois)
Os Cenários de "Não Escolha Isso"
Às vezes o melhor conselho é saber o que não escolher:
❌ NÃO use GraphQL quando:
- Seus dados são simples e planos (apps CRUD)
- Você precisa de HTTP caching agressivo
- Seu time não tem experiência com GraphQL
- Você tem um frontend com necessidades de dados previsíveis
❌ NÃO use tRPC quando:
- Seu cliente não é TypeScript
- Você precisa de uma API pública
- Cliente e servidor estão em repos diferentes com ciclos de deploy diferentes
- Você tem apps mobile consumindo a mesma API
❌ NÃO use gRPC quando:
- Só tem clientes de browser (funciona, mas é sofrido)
- Tem < 5 serviços (overkill)
- Seu time não quer aprender Protocol Buffers
- Precisa que humanos leiam o wire format pra debugar
❌ NÃO use REST quando:
- Seu frontend tem requisitos de dados aninhados e variáveis
- Tá construindo uma app monorepo TypeScript (tRPC é estritamente melhor)
- Precisa de streaming bidirecional em tempo real
Caminhos de Migração: Você Não Tá Preso
O maior medo é escolher errado e ficar travado. Boa notícia: caminhos de migração existem e já foram bem trilhados:
REST → GraphQL
// Envolva seus endpoints REST existentes como resolvers GraphQL const resolvers = { Query: { user: async (_, { id }) => { const res = await fetch(`${REST_BASE}/users/${id}`); return res.json(); }, orders: async (_, { userId }) => { const res = await fetch(`${REST_BASE}/users/${userId}/orders`); return res.json(); }, }, }; // Migre resolvers gradualmente pra acesso direto ao DB // Migração do cliente: uma query por vez
REST → tRPC
// tRPC pode coexistir com REST no mesmo servidor import { createExpressMiddleware } from '@trpc/server/adapters/express'; const app = express(); // Rotas REST existentes continuam funcionando app.get('/api/v1/users/:id', existingHandler); // Novo router tRPC montado ao lado app.use('/trpc', createExpressMiddleware({ router: appRouter })); // Migre endpoints um por um
GraphQL → tRPC
// Se você tá num monorepo TypeScript, a mudança é direta: // 1. Defina procedures tRPC que correspondam às suas queries GraphQL // 2. Migre um componente por vez // 3. Remova resolvers GraphQL que não são mais usados // Before (GraphQL): const { data } = useQuery(gql` query GetUser($id: ID!) { user(id: $id) { name, email } } `); // After (tRPC): const { data } = trpc.user.getById.useQuery({ id }); // Mesmo resultado, zero codegen, feedback de tipos instantâneo
Análise de Custos: Os Gastos Escondidos
Além das horas de desenvolvimento, cada protocolo tem implicações de custo de infraestrutura:
Comparação de custos de infra (em escala: 10M requests/dia):
REST GraphQL tRPC gRPC
────────────────── ────────── ────────── ────────── ──────────
CDN caching Excelente Ruim Bom N/A
Bandwidth Baseline -20-30% ~Baseline -60-80%
Server CPU Baseline +20-40% ~Baseline -10-20%
Custos de tooling Grátis $$ Grátis $
Monitoramento Standard Especializado Standard Especializado
Gateway/proxy Standard GraphQL GW Standard gRPC proxy
Custos escondidos:
REST: Manutenção de versionamento de API
GraphQL: Análise de complexidade de queries, rate limiting por custo
tRPC: Nada além da dependência de TypeScript
gRPC: Gerenciamento de protos, service mesh
O custo escondido do GraphQL: Em escala, você precisa de análise de complexidade de queries, persisted queries, depth limiting e ferramentas de APM especializadas. Esse imposto de infraestrutura é real e frequentemente ignorado.
A economia escondida do gRPC em bandwidth: Se o tráfego service-to-service é seu maior custo (comum em microserviços), o encoding binário do gRPC pode cortar custos de bandwidth em 60-80%.
O Veredito pra 2026
A resposta curta pro apressado:
| Cenário | Melhor Escolha | Segunda Opção |
|---|---|---|
| API pública | REST + OpenAPI | GraphQL |
| Monorepo TypeScript SaaS | tRPC | REST |
| Multi-plataforma (web + mobile + 3rd party) | GraphQL | REST |
| Microserviços (internos) | gRPC | REST |
| App CRUD simples | REST | tRPC |
| Dados real-time (bidirecional) | gRPC | GraphQL (subscriptions) |
| Dashboard admin pesado em dados | GraphQL | tRPC |
| Prototipagem / MVP | REST | tRPC |
A coisa mais importante pra entender: isso não é religião. Os melhores times de 2026 usam múltiplos protocolos pra diferentes camadas. Sua API pública pode ser REST enquanto seu frontend interno usa tRPC e seus microserviços de backend se comunicam por gRPC. São ferramentas, não identidades.
Para de discutir sobre qual protocolo é "objetivamente melhor." Comece a perguntar: "Quem consome essa API, quais são as restrições dessas pessoas, e o que meu time já sabe?"
Essa pergunta — não uma tabela comparativa — é o que deveria guiar sua decisão.
Explore ferramentas relacionadas
Experimente estas ferramentas gratuitas do Pockit