Existe uma pergunta que aparece em quase toda discussão de arquitetura: "REST, gRPC ou GraphQL?". E na maioria das vezes, a resposta vem carregada de opinião forte — como se você precisasse jurar lealdade a um protocolo e abandonar o resto.
A real é que essa pergunta tá errada. Os três existem pra resolver problemas diferentes. E a decisão de qual usar deveria ser tão pragmática quanto escolher entre um martelo e uma chave de fenda. Você não pergunta "qual ferramenta é melhor?" — você olha pro parafuso e pega a certa.
Nesse artigo, vou destrinchar o que cada protocolo faz por baixo dos panos, onde cada um se encaixa de verdade, e como você pode (e provavelmente deveria) usar mais de um na mesma aplicação.
O que tá por baixo: como cada protocolo funciona
Antes de falar de quando usar, vale entender como cada um funciona. Porque é aí que a decisão fica óbvia.
REST: texto sobre HTTP/1.1
REST opera sobre HTTP/1.1 na grande maioria dos casos. A comunicação é feita em texto (JSON, geralmente): o client serializa um objeto em string, manda pela rede, e o server desserializa de volta pra objeto. O caminho de volta é o mesmo processo invertido.
O modelo é request-response: uma requisição, uma resposta, conexão encerrada (ou reutilizada via keep-alive, mas o fluxo é o mesmo). Cada recurso tem uma URL, cada operação tem um verbo HTTP. Simples, previsível, stateless.
Essa simplicidade é proposital. O HTTP já te dá cache nativo por URL, status codes padronizados, headers pra controle de acesso, negociação de conteúdo. E como JSON é texto legível, debugar é abrir o DevTools e olhar. Qualquer dev, em qualquer linguagem, consegue consumir uma API REST em minutos.
gRPC: binário sobre HTTP/2
gRPC funciona numa camada completamente diferente. A comunicação usa Protocol Buffers — um formato binário onde você define um schema (arquivo .proto) que descreve a estrutura exata dos dados: tipos, campos, ordem. Esse schema é compilado e gera código automaticamente no client e no server, independente da linguagem.
1syntax = "proto3"; 2 3service UserService { 4 rpc GetUser (UserRequest) returns (UserResponse); 5 rpc StreamOrders (UserRequest) returns (stream Order); 6} 7 8message UserRequest { 9 string id = 1; 10} 11 12message UserResponse { 13 string name = 1; 14 string email = 2; 15} 16 17message Order { 18 string id = 1; 19 double total = 2; 20}
O resultado é um payload binário que pode ser 3x a 10x menor que o equivalente em JSON. Não tem chaves, não tem aspas, não tem nomes de campo repetidos — só dados compactados na ordem que o schema definiu.
Além disso, gRPC roda sobre HTTP/2, que permite multiplexação: múltiplos streams na mesma conexão TCP. Na prática, isso significa que um client pode disparar várias chamadas simultâneas sem abrir novas conexões, e o server pode mandar dados de volta de forma contínua — streaming bidirecional real, sem gambiarra.
GraphQL: query language sobre HTTP
GraphQL é uma linguagem de consulta. O server expõe um schema tipado (types, queries, mutations, subscriptions), e o client manda uma query declarativa dizendo exatamente que dados quer:
1query { 2 user(id: "123") { 3 name 4 email 5 orders(last: 3) { 6 id 7 total 8 } 9 notifications(unread: true) { 10 message 11 } 12 } 13}
O server resolve cada campo usando resolvers — funções que sabem buscar aquele dado específico — e retorna um JSON com exatamente a estrutura que o client pediu.
Tudo passa por um único endpoint (geralmente /graphql, via POST). Não tem URL por recurso, não tem verbo HTTP com semântica. A inteligência tá na query, não na rota.
Onde cada um brilha (e onde tropeça)
REST: o canivete suíço
Brilha quando:
- Você precisa de uma API pública que terceiros vão consumir. REST é universalmente entendido — todo mundo sabe fazer um GET.
- O domínio é CRUD simples: criar, ler, atualizar, deletar recursos bem definidos.
- Você quer cache HTTP nativo. Um GET em
/users/123com headerCache-Controlfunciona out-of-the-box em CDNs, proxies reversos, browsers. - O time é pequeno e você controla frontend e backend. Não precisa de mais complexidade.
Tropeça quando:
- O client precisa de flexibilidade nos dados. Você acaba criando endpoints específicos (
/users/123/summary,/users/123/full) ou aceitando query params pra filtrar campos — e isso vira uma bagunça rápido. - A comunicação é entre serviços internos com alto volume. JSON é verboso, serialização é cara, e HTTP/1.1 não multiplica bem.
- Você precisa de streaming real. Dá pra fazer Server-Sent Events ou WebSocket por cima, mas aí você tá saindo do REST.
gRPC: a via expressa entre serviços
Brilha quando:
- Dois (ou mais) backends precisam conversar com baixa latência e alto throughput. O formato binário e HTTP/2 fazem diferença real quando são milhões de requests por minuto.
- Você precisa de contrato forte. O arquivo
.protoé a fonte de verdade — se o schema muda, o código gerado quebra em compilação, não em runtime. Zero surpresa. - O cenário pede streaming: IoT mandando telemetria contínua, pipelines de dados financeiros, logs em tempo real. gRPC suporta streaming unidirecional e bidirecional nativamente.
- Você trabalha num ambiente poliglota. Um serviço em Go conversando com outro em C# e outro em Python. O protobuf gera o client correto pra cada linguagem a partir do mesmo schema.
Tropeça quando:
- Precisa funcionar no browser. gRPC-Web existe, mas é um proxy extra na frente — funciona, mas não é o uso ideal.
- Você quer debugar rápido. Não dá pra abrir o DevTools e ler o payload. Precisa de ferramentas como
grpcurlou reflection. - O time não tem familiaridade com protobufs e o overhead de aprendizado não se justifica pra complexidade do projeto.
GraphQL: o contrato flexível
Brilha quando:
- Você tem múltiplos clientes com necessidades diferentes do mesmo backend. Um app mobile que precisa de 3 campos, um dashboard web que precisa de 20, uma smart TV que precisa de 5 — todos consultam o mesmo schema.
- O frontend muda rápido. Em vez de pedir pro backend criar um endpoint novo a cada feature, o frontend ajusta a query e segue.
- Seu domínio tem relações profundas entre entidades. Buscar um usuário com seus pedidos, com os itens de cada pedido, com o fornecedor de cada item — numa query só, sem N+1 de requisições HTTP.
Tropeça quando:
- A complexidade migra pro server. Cada campo precisa de um resolver. Uma query aninhada demais pode disparar centenas de consultas ao banco (o famoso N+1 problem do GraphQL). Sem rate limiting e depth limiting, um client mal-intencionado (ou só descuidado) pode derrubar o server.
- Cache fica difícil. REST cacheia por URL — simples, nativo. GraphQL manda tudo por POST no mesmo endpoint, então cache HTTP não funciona. Ferramentas como Apollo Client e Relay resolvem no client, mas é mais infraestrutura.
- Você tem um frontend e um backend, ambos no mesmo time. O overhead de manter um schema GraphQL, escrever resolvers, configurar tooling... pra quê? Um REST bem feito resolve com muito menos cerimônia.
Na prática: misturando protocolos
O cenário mais comum em aplicações de escala média/grande não é escolher um protocolo — é usar cada um onde faz sentido.
┌──────────────┐ REST/GraphQL ┌──────────────────┐
│ Browser / │ ◄──────────────────────► │ API Gateway / │
│ Mobile App │ │ BFF │
└──────────────┘ └────────┬─────────┘
│
gRPC │ gRPC
┌──────────────┼──────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Service A│ │ Service B│ │ Service C│
│ (Users) │ │ (Orders) │ │ (Notify) │
└──────────┘ └──────────┘ └──────────┘
- REST na camada externa, expondo APIs públicas e documentadas
- GraphQL como BFF (Backend For Frontend) quando os clients têm necessidades muito diferentes
- gRPC na comunicação interna entre microsserviços, onde performance importa e ninguém tá debugando no browser
Não tem nada de errado em ter os três rodando na mesma aplicação. Na real, é sinal de que você tá fazendo escolhas intencionais em vez de seguir hype.
O framework de decisão
Se você tiver que resumir numa regra simples:
- Comece com REST. Sério. Pra 80% dos cenários, resolve. É simples, todo mundo conhece, tem tooling maduro.
- Quando a dor aparecer, reaja. Se a comunicação entre serviços tá lenta e o payload tá grande demais, olha pro gRPC. Se os frontends estão pedindo endpoints custom toda semana, olha pro GraphQL.
- Não adote por moda. gRPC num projeto com dois endpoints é overengineering. GraphQL pra servir um CRUD simples é complexidade desnecessária.
A pergunta nunca deveria ser "qual é o melhor protocolo?" — deveria ser "qual é o problema que eu preciso resolver agora?"
Quem aprende a pensar assim para de ser dev que segue tutorial e começa a tomar decisão de arquitetura.