menos confusão, mais clareza.
Antes de decorar verbos HTTP ou schemas GraphQL, você precisa entender o mapa — o que é API, o que é RPC, e como REST, GraphQL e gRPC se encaixam nisso tudo. Esse é o ponto de partida.
API (Application Programming Interface) é qualquer interface que permite que dois programas se comuniquem. É o "quê": o contrato de como acessar uma funcionalidade. REST, GraphQL e gRPC são todos formas de construir APIs.
RPC (Remote Procedure Call) é o "como": a abordagem de chamar funções remotas como se fossem locais. RPC não é uma API — ele implementa uma API. O gRPC usa RPC por baixo para construir APIs de alta performance.
REST pensa em recursos: GET /pedidos/99 — "me dê o pedido 99". gRPC pensa em ações: GetPedido(id: 99) — "execute essa função no servidor remoto". O resultado final é parecido, mas a filosofia de design é completamente diferente.
| Critério | REST | GraphQL | gRPC |
|---|---|---|---|
| Protocolo | HTTP/1.1 | HTTP/1.1 ou 2 | HTTP/2 obrigatório |
| Formato | JSON | JSON | Protobuf (binário) |
| Controle do payload | Servidor decide | ✓ Cliente decide | Contrato fixo |
| Performance | Boa | Boa | ✓ Muito alta |
| Suporte no browser | ✓ Nativo | ✓ Nativo | Precisa de proxy |
| Pensa em termos de | Recursos / dados | Campos / grafo | Funções / ações |
| Melhor uso | APIs públicas | Apps mobile, BFFs | Microsserviços internos |
A semântica está no verbo HTTP, não no nome da rota. Exemplo real: e-commerce.
200 OK201 Created204 No Content400 Bad Request401 Unauthorized404 Not FoundRoy Fielding definiu REST em sua dissertação de 2000. Estes são os pilares que tornam uma API verdadeiramente RESTful.
/usuarios/42 sempre é o mesmo usuário.Accept.Cache-Control e ETag.Estado é qualquer informação que precisa ser lembrada entre uma requisição e a próxima. A diferença está em quem guarda esse estado.
O servidor precisava lembrar o contexto. Quando se perdeu, tudo quebrou.
Cada mensagem carrega tudo. Qualquer garçom pode atender — não importa quem estava antes.
POST /login
{ "usuario": "ana", "senha": "123" }
// Servidor cria na memória:
// session["abc123"] = { userId: 7 }
Set-Cookie: sessionId=abc123
POST /carrinho/adicionar
Cookie: sessionId=abc123
{ "produtoId": 42 }
// Busca sessão na memória ✓
GET /carrinho Cookie: sessionId=abc123 // ❌ Servidor 2 não tem a sessão // → carrinho vazio ou 401
POST /login
{ "usuario": "ana", "senha": "123" }
// Servidor NÃO guarda nada.
// Gera token assinado:
{ "token": "eyJhbGci...ana.7" }
POST /carrinho/itens
Authorization: Bearer eyJ...
{ "produtoId": 42 }
// Valida token, extrai userId=7
GET /carrinho Authorization: Bearer eyJ... // ✓ Qualquer servidor valida // → 200 OK + dados do banco
Muitos sistemas usam Redis para armazenar sessões — ainda é stateful, mas o estado sai da RAM do servidor e vai para armazenamento centralizado. Resolve o scale, mas exige infra extra. Trade-off: JWT é mais simples mas não pode ser revogado imediatamente; Redis pode invalidar na hora.
# Token = 3 partes em Base64, separadas por ponto eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjcsInJvbGUiOiJhZG1pbiIsImV4cCI6MTcxOH0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c Header: { "alg": "HS256", "typ": "JWT" } Payload: { "userId": 7, "role": "admin", "exp": 1718000000 } Assinatura: HMAC-SHA256(header + "." + payload, SECRET)
O servidor valida a assinatura com sua chave secreta. Se alguém alterar o payload, a assinatura não bate — token rejeitado automaticamente.
POST /loginAuthorization: Bearer eyJ...Funciona entre domínios (CORS)
Expira via campo
expCarrega role e permissions
Payload apenas codificado (Base64),
não criptografado
const jwt = require('jsonwebtoken'); function autenticar(req, res, next) { const token = req.headers.authorization?.split(' ')[1]; if (!token) return res.status(401).json({ erro: 'Token ausente' }); try { // Valida assinatura e extrai payload — zero banco const payload = jwt.verify(token, process.env.JWT_SECRET); req.userId = payload.userId; req.role = payload.role; next(); } catch { res.status(401).json({ erro: 'Token inválido ou expirado' }); } }
A diferença entre design RESTful e um estilo RPC-like onde tudo vira verbo na URL.
POST /buscarProdutos POST /criarProduto POST /deletarProduto POST /atualizarProduto GET /getPedidosByUser?userId=7 POST /cancelarPedido
GET /produtos POST /produtos DELETE /produtos/42 PUT /produtos/42 GET /usuarios/7/pedidos DELETE /pedidos/99
No modelo RESTful, qualquer desenvolvedor que chegue na API já sabe o que DELETE /pedidos/99 faz — é autoexplicativo. No modelo RPC, você precisa ler a documentação de cada endpoint individualmente. REST usa o protocolo HTTP como vocabulário, tornando a API previsível e consistente.
Simule chamadas REST para a loja e veja a resposta. Exatamente como apresentado no chat.
Nasceu no Facebook em 2012 para resolver um problema específico: apps mobile precisavam de dados muito precisos, mas as APIs REST devolviam dados demais (overfetching) ou de menos (underfetching).
POST /graphql. Não há múltiplas rotas.O schema define o que existe. A query define o que você quer. São documentos separados — servidor e cliente têm papéis distintos.
type Produto { id: ID! nome: String! preco: Float! categoria: String estoque: Int tags: [String] } type Query { produto(id: ID!): Produto produtos: [Produto!]! } type Mutation { criarProduto(nome: String!, preco: Float!): Produto deletarProduto(id: ID!): Boolean }
O ! significa campo obrigatório (non-null). [Produto!]! = lista não-nula de produtos não-nulos.
{
produto(id: "42") {
nome
preco
}
}
{
produto(id: "42") {
nome
preco
categoria
estoque
tags
}
}
{
usuario(id: "7") { nome email }
pedidosDoUsuario(userId: "7") { id status total }
recomendados(userId: "7") { nome preco }
}
Mutations são o equivalente ao POST/PUT/DELETE do REST — separadas explicitamente das queries de leitura.
# Criar produto mutation { criarProduto(nome: "Novo Produto", preco: 99.90) { id nome } } # Atualizar com variáveis mutation AtualizarEmail($id: ID!, $email: String!) { atualizarUsuario(id: $id, email: $email) { id email } }
Usam WebSockets para o servidor empurrar dados ao cliente quando algo muda — sem polling.
subscription { pedidoAtualizado(pedidoId: "99") { status updatedAt } } # Servidor empurra quando muda: # { "data": { "pedidoAtualizado": { # "status": "enviado", # "updatedAt": "2025-06-11T14:30:00Z" }}}
Selecione os campos que seu cliente precisa e veja a economia real em bytes comparado ao REST.
{
"id": "42",
"nome": "Tênis Air Max",
"preco": 349.90,
"categoria": "calçados",
"estoque": 23,
"peso_kg": 0.8,
"criado_em": "2024-01-10",
"tags": ["esporte","corrida"],
"fornecedor_id": "F001",
"sku": "TEN-AM-42-PRE"
}
Queries muito complexas podem sobrecarregar o banco — um cliente pode pedir produtos → pedidos → usuários → pedidos de cada usuário → produtos de cada pedido, criando um grafo enorme. Exige proteção com depth limit e query cost analysis.
Antes de entender o gRPC, é preciso entender o conceito mais antigo no qual ele se baseia: Remote Procedure Call — Chamada de Procedimento Remoto.
RPC é um modelo de comunicação onde um programa chama uma função que roda em outro computador — mas o código parece uma chamada de função local. O programador não precisa pensar em sockets, serialização ou protocolo de rede. Ele simplesmente chama uma função.
# Função está no mesmo processo resultado = calcularFrete( origem="SP", destino="RJ", peso=2.5 ) print(resultado) # R$ 18.90
# Função roda em outro servidor! # O código parece idêntico: resultado = calcularFrete( origem="SP", destino="RJ", peso=2.5 ) print(resultado) # R$ 18.90
Por baixo, a chamada RPC viaja pela rede, é executada no servidor remoto e o resultado volta. Mas o desenvolvedor vê apenas uma função sendo chamada.
Pense no RPC como ligar para um serviço de informações. Você fala um pedido ("qual o horário do voo 1234?"), alguém do outro lado consulta o sistema e te passa a resposta. Você não sabe como eles buscaram — só sabe que pediu e recebeu.
| Aspecto | RPC | REST |
|---|---|---|
| Pensa em termos de | Ações / funções calcularFrete(), criarPedido() | Recursos / dados /pedidos, /fretes |
| URL típica | /CalcularFrete/CriarPedido | GET /fretesPOST /pedidos |
| Verbo HTTP | Geralmente só POST | GET, POST, PUT, DELETE… |
| Abstração | Procedimento remoto | Estado de um recurso |
| Exemplo mental | "Faça isso para mim" | "Me dê / guarde este recurso" |
Cada geração resolveu problemas da anterior. O gRPC é a versão moderna — com HTTP/2, Protocol Buffers e suporte nativo a streaming.
O gRPC não surgiu do nada. É a evolução de décadas de tentativas de fazer chamadas remotas de forma eficiente. Entender o problema que ele resolve torna tudo mais claro.
Nas décadas de 1990 e 2000, o padrão era SOAP (Simple Object Access Protocol) — um formato RPC baseado em XML. Funcionava, mas era verboso ao extremo, difícil de debugar e lento.
<!-- 📦 SOAP Request — buscar produto --> <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"> <soapenv:Body> <loja:GetProduto> <loja:id>42</loja:id> </loja:GetProduto> </soapenv:Body> </soapenv:Envelope> <!-- vs gRPC — a mesma chamada em Protobuf binário --> # 0x0A 02 34 32 (4 bytes — id="42")
Imagine 50 microsserviços, cada um chamando outros 5 em média, com 10.000 requisições por segundo. Com REST/JSON:
• Serialização JSON é lenta (texto → objeto → texto)
• HTTP/1.1 abre uma conexão TCP por requisição
• Headers HTTP repetidos em toda chamada
Com gRPC:
• Protobuf serializa em binário — até 10× mais rápido
• HTTP/2 reutiliza a mesma conexão para múltiplas chamadas
• Headers comprimidos com HPACK
.proto é o contrato — versionado no Git, gera código automaticamente, e qualquer mudança incompatível é detectada na compilação, não em produção..proto..proto gera stubs tipados em todas as linguagens — o contrato é o código, não o documento.| Cenário | REST é ok | gRPC é melhor |
|---|---|---|
| API pública para terceiros | ✓ Fácil de consumir | ✗ Binário dificulta |
| 100k req/s entre serviços internos | ✗ JSON/HTTP1.1 pesado | ✓ Binário + HTTP/2 |
| Stream contínuo de dados (IoT) | ✗ Gambiarra com SSE | ✓ Streaming nativo |
| Times com linguagens diferentes | ✗ Cada um implementa | ✓ Stubs gerados do .proto |
| Debug e inspeção manual | ✓ JSON legível | ✗ Binário ilegível |
| App mobile / browser direto | ✓ Suporte nativo | ✗ Precisa de gRPC-Web |
O gRPC surgiu porque o Google precisava de uma forma de fazer microsserviços se comunicarem com performance máxima, contrato rígido e suporte a múltiplas linguagens — e nenhuma das soluções existentes entregava os três ao mesmo tempo.
Agora que você entende RPC e por que o gRPC surgiu, veja como ele funciona por dentro: Protocol Buffers, HTTP/2 e streaming bidirecional.
HTTP/2 com multiplexação — várias chamadas simultâneas em uma só conexão TCP.
Protocol Buffers (binário) → até 10× menor e mais rápido que JSON equivalente.
Cliente e servidor enviam múltiplas mensagens em ambas as direções simultaneamente.
grpcurl. E o browser não suporta gRPC nativo: precisa de gRPC-Web proxy.O arquivo .proto é o coração do gRPC. Define tipos e serviços. A partir dele, o código cliente e servidor é gerado automaticamente em qualquer linguagem.
syntax = "proto3"; message Produto { string id = 1; string nome = 2; float preco = 3; int32 estoque = 4; } message ProdutoRequest { string id = 1; } service ProdutoService { rpc GetProduto(ProdutoRequest) returns (Produto); rpc ListarProdutos(Empty) returns (stream Produto); }
Os números (= 1, = 2...) são IDs de campo usados na serialização binária. Nunca mude um número em produção — quebra compatibilidade com clientes antigos.
# Stubs gerados: python -m grpc_tools.protoc import grpc import catalogo_pb2, catalogo_pb2_grpc channel = grpc.insecure_channel('catalogo:50051') stub = catalogo_pb2_grpc.ProdutoServiceStub(channel) # Chamada unary resp = stub.GetProduto(catalogo_pb2.ProdutoRequest(id="42")) print(resp.nome, resp.preco) # Chamada streaming for produto in stub.ListarProdutos(catalogo_pb2.ListaVazia()): print(produto.nome)
const grpc = require('@grpc/grpc-js'); const protoLoader = require('@grpc/proto-loader'); const def = protoLoader.loadSync('catalogo.proto'); const proto = grpc.loadPackageDefinition(def).catalogo; const client = new proto.ProdutoService( 'catalogo:50051', grpc.credentials.createInsecure() ); client.GetProduto({ id: '42' }, (err, produto) => { console.log(produto.nome, produto.preco); });
// protoc --go_out=. --go-grpc_out=. catalogo.proto conn, _ := grpc.Dial("catalogo:50051", grpc.WithInsecure()) defer conn.Close() client := pb.NewProdutoServiceClient(conn) produto, _ := client.GetProduto( context.Background(), &pb.ProdutoRequest{Id: "42"}, ) fmt.Println(produto.Nome, produto.Preco)
Estado é qualquer informação que precisa ser lembrada entre uma requisição e a próxima. A diferença está em quem guarda esse estado.
O servidor precisava lembrar o contexto de todas as mensagens anteriores. Quando o contexto se perdeu, tudo quebrou.
Cada mensagem carrega tudo que o servidor precisa. Qualquer garçom pode atender — não importa quem estava antes.
A diferença prática entre uma API que guarda sessão no servidor e uma que delega o estado ao cliente via token.
POST /login
{ "usuario": "ana", "senha": "123" }
// Servidor cria na memória:
// session["abc123"] = { userId: 7 }
Set-Cookie: sessionId=abc123
POST /carrinho/adicionar
Cookie: sessionId=abc123
{ "produtoId": 42 }
// Busca sessão na memória ✓
GET /carrinho Cookie: sessionId=abc123 // ❌ Servidor 2 não tem a sessão // → carrinho vazio ou 401
POST /login
{ "usuario": "ana", "senha": "123" }
// Servidor NÃO guarda nada.
// Gera token assinado:
{ "token": "eyJhbGci...ana.7" }
POST /carrinho/itens
Authorization: Bearer eyJ...
{ "produtoId": 42 }
// Valida token, extrai userId=7
GET /carrinho Authorization: Bearer eyJ... // ✓ Qualquer servidor valida // → 200 OK + dados do banco
Cada um desses problemas aparece em produção. Clique para expandir.
Muitos sistemas usam Redis para armazenar sessões — ainda é tecnicamente stateful, mas melhor: o estado sai da memória local do servidor e vai para um armazenamento centralizado. Resolve o scale, mas ainda é mais complexo que JWT puro. A escolha depende de um trade-off: JWT é mais simples, mas não pode ser revogado imediatamente; sessão em Redis pode ser invalidada na hora, mas exige a infraestrutura extra.
JSON Web Token é o padrão para autenticação stateless. O token carrega tudo que o servidor precisa — sem consultar banco ou sessão.
# Token = 3 partes separadas por ponto, cada uma em Base64 eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjcsInJvbGUiOiJhZG1pbiIsImV4cCI6MTcxOH0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c Header: { "alg": "HS256", "typ": "JWT" } Payload: { "userId": 7, "role": "admin", "exp": 1718000000 } Assinatura: HMAC-SHA256(header + "." + payload, SECRET)
O servidor valida a assinatura com sua chave secreta. Se alguém alterar o payload, a assinatura não bate — token rejeitado automaticamente.
POST /loginAuthorization: Bearer eyJ...Funciona entre domínios (microsserviços, CORS)
Expira automaticamente via campo
expCarrega dados extras (role, permissions)
Solução: blacklist em Redis (mas fica stateful)
Payload é codificado (Base64), não criptografado
const jwt = require('jsonwebtoken'); function autenticar(req, res, next) { const token = req.headers.authorization?.split(' ')[1]; if (!token) return res.status(401).json({ erro: 'Token ausente' }); try { // Valida assinatura e extrai payload — zero banco const payload = jwt.verify(token, process.env.JWT_SECRET); req.userId = payload.userId; req.role = payload.role; next(); } catch { res.status(401).json({ erro: 'Token inválido ou expirado' }); } } // Uso em qualquer rota app.get('/perfil', autenticar, (req, res) => { res.json({ mensagem: `Olá, usuário ${req.userId}` }); });
Tabela completa com todos os critérios relevantes, exatamente como apresentado no comparativo do chat.
| Característica | GraphQL | gRPC |
|---|---|---|
| Protocolo | HTTP/1.1 ou 2 | HTTP/2 obrigatório |
| Formato | JSON (legível) | Protobuf (binário) |
| Performance | Boa | Muito alta (~10× menor) |
| Flexibilidade de query | ✓ Cliente define a forma | ✗ Contrato fixo no .proto |
| Streaming | Subscriptions (WebSocket) | ✓ Nativo bidirecional |
| Suporte no browser | ✓ Nativo | Precisa de proxy |
| Tipagem | Schema SDL | Protobuf (mais rígido) |
| Melhor uso | APIs públicas, BFFs, mobile | Microsserviços internos |
| Critério | REST | GraphQL | gRPC |
|---|---|---|---|
| Protocolo | HTTP/1.1 | HTTP/1.1 ou 2 | HTTP/2 obrigatório |
| Formato de dados | JSON / XML | JSON | Protobuf (binário) |
| Controle do payload | Servidor decide | ✓ Cliente decide | Contrato fixo |
| Overfetching | ✗ Sim | ✓ Não | N/A |
| Múltiplas chamadas | ✗ Possível | ✓ Query única | Depende |
| Performance | Boa | Boa | ✓ Muito alta |
| Streaming | Limitado | Subscriptions (WS) | ✓ Bidirecional nativo |
| Suporte no browser | ✓ Nativo | ✓ Nativo | Precisa de proxy |
| Depuração | ✓ Muito fácil | Média | Difícil (binário) |
| Curva de aprendizado | Baixa | Média | Alta |
Na prática, arquiteturas modernas usam as três abordagens juntas — cada uma no papel certo.
Um sistema moderno tipicamente combina as três em camadas:
As quatro abas do explorador original do chat, reunidas em uma única tela.
type Produto { id: ID! nome: String! preco: Float! categoria: String estoque: Int } type Query { produto(id: ID!): Produto produtos: [Produto!]! }
{
produto(id: "42") {
nome
preco
}
}
{
produto(id: "42") {
nome
preco
categoria
estoque
}
}
# Em REST precisaria de 3 chamadas. No GraphQL: uma só. { usuario(id: "7") { nome email } pedidosDoUsuario(userId: "7") { id status total } recomendados(userId: "7") { nome preco } }
syntax = "proto3"; message Produto { string id = 1; string nome = 2; float preco = 3; int32 estoque = 4; } message ProdutoRequest { string id = 1; } service ProdutoService { rpc GetProduto(ProdutoRequest) returns (Produto); rpc ListarProdutos(Empty) returns (stream Produto); }
HTTP/2 com multiplexação — várias chamadas simultâneas em uma só conexão TCP.
Protocol Buffers (binário) → até 10× menor que JSON equivalente.
Cliente e servidor podem enviar múltiplas mensagens em ambas as direções simultaneamente.
# O stub é gerado automaticamente — tipado, validado import produto_pb2_grpc, produto_pb2 channel = grpc.insecure_channel('catalogo:50051') stub = produto_pb2_grpc.ProdutoServiceStub(channel) resp = stub.GetProduto(produto_pb2.ProdutoRequest(id="42")) print(resp.nome, resp.preco)
Simule o problema de overfetching do REST e como o GraphQL resolve — selecione os campos que o cliente precisa:
{
"id": "42",
"nome": "Tênis Air Max",
"preco": 349.90,
"categoria": "calçados",
"estoque": 23,
"peso_kg": 0.8,
"criado_em": "2024-01-10",
"tags": ["esporte","corrida"],
"fornecedor_id": "F001",
"sku": "TEN-AM-42-PRE"
}
Selecione os campos desejados:
| Característica | GraphQL | gRPC |
|---|---|---|
| Protocolo | HTTP/1.1 ou 2 | HTTP/2 obrigatório |
| Formato | JSON (legível) | Protobuf (binário) |
| Performance | Boa | Muito alta (~10× menor) |
| Flexibilidade de query | ✓ Cliente define a forma | ✗ Contrato fixo no .proto |
| Streaming | Subscriptions (WebSocket) | ✓ Nativo bidirecional |
| Suporte no browser | ✓ Nativo | Precisa de proxy |
| Tipagem | Schema SDL | Protobuf (mais rígido) |
| Melhor uso | APIs públicas, BFFs, mobile | Microsserviços internos |
| Critério | REST | GraphQL | gRPC |
|---|---|---|---|
| Protocolo | HTTP/1.1 | HTTP/1.1 ou 2 | HTTP/2 obrigatório |
| Formato de dados | JSON / XML | JSON | Protobuf (binário) |
| Controle do payload | Servidor decide | ✓ Cliente decide | Contrato fixo |
| Overfetching | ✗ Sim | ✓ Não | N/A |
| Performance | Boa | Boa | ✓ Muito alta |
| Streaming | Limitado | Subscriptions (WS) | ✓ Bidirecional nativo |
| Suporte no browser | ✓ Nativo | ✓ Nativo | Precisa de proxy |
| Depuração | ✓ Muito fácil | Média | Difícil (binário) |
| Curva de aprendizado | Baixa | Média | Alta |
Quatro cenários do mundo real. Para cada um, escolha a arquitetura de API e a arquitetura de sistema que você usaria. Ao confirmar, veja o gabarito com a justificativa completa.