Back

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árioMelhor EscolhaSegunda Opção
API públicaREST + OpenAPIGraphQL
Monorepo TypeScript SaaStRPCREST
Multi-plataforma (web + mobile + 3rd party)GraphQLREST
Microserviços (internos)gRPCREST
App CRUD simplesRESTtRPC
Dados real-time (bidirecional)gRPCGraphQL (subscriptions)
Dashboard admin pesado em dadosGraphQLtRPC
Prototipagem / MVPRESTtRPC

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.

RESTGraphQLtRPCgRPCAPI DesignSystem DesignTypeScriptNode.jsArchitectureWeb Development

Explore ferramentas relacionadas

Experimente estas ferramentas gratuitas do Pockit