Resposta rápida: use gRPC-Gateway em Go quando o serviço já tem contratos Protobuf e precisa atender também clientes que falam HTTP/JSON. Você anota os métodos gRPC com google.api.http, gera um runtime.ServeMux e publica rotas REST como GET /v1/users/{id} sem manter DTOs e handlers duplicados. A solução funciona melhor quando gRPC é o contrato principal; se a API pública tem semântica, payloads ou ciclo de vida muito diferentes, mantenha uma camada REST própria.
gRPC é uma ótima escolha para comunicação interna entre serviços, mas browsers, integrações externas, ferramentas de linha de comando e equipes parceiras frequentemente preferem REST com JSON. Criar as duas interfaces manualmente costuma gerar drift: o campo muda no .proto, mas não no DTO HTTP; um código de erro é corrigido no gRPC, mas continua diferente no REST; a documentação deixa de representar a implementação.
O gRPC-Gateway reduz essa duplicação ao gerar uma camada de transcodificação. A requisição HTTP entra pelo gateway, vira uma mensagem Protobuf, chama o serviço gRPC e volta como JSON. Este guia mostra a arquitetura, um exemplo completo, geração com Buf, autenticação, erros, OpenAPI, testes e critérios para decidir entre gRPC-Gateway, REST manual e ConnectRPC.
Se você ainda não domina a base, leia primeiro gRPC em Go com Protobuf e o tutorial completo de Go e gRPC. Para APIs puramente HTTP, o caminho mais simples continua sendo API REST com Go e net/http.
Quando usar gRPC-Gateway
| Cenário | Recomendação |
|---|---|
| Serviço interno já usa gRPC e precisa expor JSON | gRPC-Gateway |
| Um contrato Protobuf deve gerar gRPC, REST e OpenAPI | gRPC-Gateway |
| API pública tem recursos e payloads muito diferentes do RPC interno | REST separado |
| Projeto pequeno só precisa de HTTP/JSON | net/http, Chi, Gin ou Echo |
| Mesmo handler deve falar Connect, gRPC e gRPC-Web | Avalie ConnectRPC |
| Gateway precisa escalar e receber políticas de borda próprias | Processo separado |
| Time quer deploy simples para uma aplicação pequena | Gateway no mesmo binário |
O principal sinal favorável é a existência de uma fonte de verdade em Protobuf. Se você ainda não usa gRPC e só quer gerar uma API REST, começar por OpenAPI pode ser mais direto; veja OpenAPI com oapi-codegen.
Como o gRPC-Gateway funciona
A arquitetura mais comum tem três etapas:
Cliente HTTP/JSON
|
| GET /v1/users/42
v
runtime.ServeMux (gRPC-Gateway)
|
| user.v1.UserService/GetUser
v
Servidor gRPC-Go
|
v
Serviço de domínio + banco
O código gerado conhece o mapeamento entre rota HTTP e método gRPC. O gateway não deve conter regra de negócio: ele traduz transporte, propaga metadata e converte respostas. Validação de domínio, autorização e acesso a dados continuam no serviço.
Você pode executar gateway e gRPC no mesmo processo, em portas diferentes, ou publicar cada um como deployment independente. A separação é útil quando a borda HTTP precisa de WAF, rate limit, CORS ou escala diferente. O mesmo processo reduz componentes para operar e funciona bem em sistemas menores.
Definindo a rota REST no Protobuf
Considere um serviço de usuários em proto/user/v1/user.proto:
syntax = "proto3";
package user.v1;
option go_package = "example.com/accounts/gen/user/v1;userv1";
import "google/api/annotations.proto";
service UserService {
rpc GetUser(GetUserRequest) returns (GetUserResponse) {
option (google.api.http) = {
get: "/v1/users/{id}"
};
}
rpc CreateUser(CreateUserRequest) returns (CreateUserResponse) {
option (google.api.http) = {
post: "/v1/users"
body: "*"
};
}
}
message GetUserRequest {
string id = 1;
}
message GetUserResponse {
string id = 1;
string name = 2;
string email = 3;
}
message CreateUserRequest {
string name = 1;
string email = 2;
}
message CreateUserResponse {
string id = 1;
string name = 2;
string email = 3;
}
A anotação get: "/v1/users/{id}" liga o parâmetro de caminho ao campo id. No POST, body: "*" informa que o corpo JSON preenche a mensagem inteira. Para requests com metadata fora do corpo, você pode apontar body para um campo específico.
Evite desenhar a rota como tradução mecânica do nome do RPC, por exemplo /callGetUser. Mesmo usando RPC por baixo, a interface HTTP deve respeitar convenções reconhecíveis: recursos no plural, métodos HTTP coerentes, query string para filtros e status adequados.
Geração reproduzível com Buf
É possível chamar protoc diretamente, mas Buf facilita dependências, lint, breaking-change detection e geração. Um buf.yaml mínimo:
version: v2
modules:
- path: proto
deps:
- buf.build/googleapis/googleapis
lint:
use:
- STANDARD
breaking:
use:
- FILE
O buf.gen.yaml declara os plugins:
version: v2
plugins:
- remote: buf.build/protocolbuffers/go
out: gen
opt: paths=source_relative
- remote: buf.build/grpc/go
out: gen
opt: paths=source_relative
- remote: buf.build/grpc-ecosystem/gateway
out: gen
opt: paths=source_relative
- remote: buf.build/grpc-ecosystem/openapiv2
out: gen/openapi
Gere os arquivos com:
buf dep update
buf lint
buf generate
O exemplo omite versões dos plugins para manter a leitura curta. No repositório real, fixe as versões aprovadas no remote ou use plugins locais instalados por uma ferramenta versionada; sem isso, duas gerações em datas diferentes podem produzir diffs inesperados.
O repositório passa a receber, entre outros artefatos, o código Protobuf, o servidor/cliente gRPC e o arquivo *.pb.gw.go do gateway. No CI, execute buf lint, verifique breaking changes conforme a política do time, rode buf generate e falhe se os arquivos versionados ficarem diferentes:
buf lint
buf breaking --against '.git#branch=main'
buf generate
git diff --exit-code -- gen
Fixe a configuração e revise atualizações de plugins. Usar @latest de forma implícita em cada máquina pode gerar diffs inesperados e builds pouco reproduzíveis.
Subindo o servidor gRPC
O servidor continua sendo gRPC-Go normal. A implementação abaixo valida a entrada e traduz erros de domínio para códigos gRPC:
package transport
import (
"context"
userv1 "example.com/accounts/gen/user/v1"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type UserService interface {
FindByID(ctx context.Context, id string) (User, error)
}
type UserServer struct {
userv1.UnimplementedUserServiceServer
users UserService
}
func (s *UserServer) GetUser(
ctx context.Context,
req *userv1.GetUserRequest,
) (*userv1.GetUserResponse, error) {
if req.GetId() == "" {
return nil, status.Error(codes.InvalidArgument, "id é obrigatório")
}
user, err := s.users.FindByID(ctx, req.GetId())
if err != nil {
if IsNotFound(err) {
return nil, status.Error(codes.NotFound, "usuário não encontrado")
}
return nil, status.Error(codes.Internal, "erro ao buscar usuário")
}
return &userv1.GetUserResponse{
Id: user.ID,
Name: user.Name,
Email: user.Email,
}, nil
}
A aplicação registra esse servidor em um grpc.Server, configura TLS quando necessário e aplica interceptors de autenticação, logs, métricas e tracing. O guia de OpenTelemetry em Go mostra como conectar a chamada HTTP à operação gRPC e ao banco.
Subindo o gateway HTTP
O gateway gerado registra as rotas em um runtime.ServeMux:
package main
import (
"context"
"log"
"net/http"
"time"
userv1 "example.com/accounts/gen/user/v1"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
func runGateway(ctx context.Context) error {
mux := runtime.NewServeMux()
opts := []grpc.DialOption{
grpc.WithTransportCredentials(insecure.NewCredentials()),
}
if err := userv1.RegisterUserServiceHandlerFromEndpoint(
ctx,
mux,
"127.0.0.1:9090",
opts,
); err != nil {
return err
}
server := &http.Server{
Addr: ":8080",
Handler: mux,
ReadHeaderTimeout: 5 * time.Second,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
IdleTimeout: 60 * time.Second,
}
log.Println("HTTP/JSON em :8080")
return server.ListenAndServe()
}
Em produção, não use credenciais inseguras para uma conexão que atravessa rede não confiável. Se gateway e gRPC estão no mesmo pod ou host, a decisão depende do modelo de ameaça; entre deployments, use TLS ou mTLS e valide nomes e certificados.
Para uma implantação no mesmo processo, você também pode registrar o handler diretamente contra a implementação, evitando uma conexão de rede local. A alternativa via endpoint é mais fácil de separar depois e exercita o caminho gRPC real.
A rota já pode ser chamada com:
curl -sS http://localhost:8080/v1/users/42
Resposta:
{
"id": "42",
"name": "Ana Souza",
"email": "[email protected]"
}
Erros HTTP e gRPC sem inconsistência
Por padrão, o gateway converte códigos gRPC para status HTTP. Exemplos esperados incluem:
| Código gRPC | HTTP típico |
|---|---|
InvalidArgument | 400 |
Unauthenticated | 401 |
PermissionDenied | 403 |
NotFound | 404 |
AlreadyExists | 409 |
ResourceExhausted | 429 |
Unavailable | 503 |
Internal | 500 |
Não retorne todo erro como Internal. O consumidor precisa distinguir entrada inválida, ausência de recurso, falta de autenticação e indisponibilidade transitória. Ao mesmo tempo, não envie detalhes de SQL, stack trace ou token na mensagem pública.
Se sua API precisa de um envelope de erro próprio, configure runtime.WithErrorHandler. Faça isso com cuidado: preserve o status correto, mantenha um formato estável e teste todos os códigos relevantes. Um corpo possível:
{
"code": "USER_NOT_FOUND",
"message": "usuário não encontrado",
"request_id": "req_01J..."
}
O código de domínio não deve depender do texto da mensagem. Mensagens mudam e podem ser traduzidas; códigos estáveis podem alimentar UI, retry e métricas.
Autenticação, headers e metadata
Uma requisição REST costuma trazer Authorization, X-Request-ID e informações de rastreamento em headers. O serviço gRPC recebe metadata. Use um matcher para controlar o que pode atravessar a fronteira, em vez de encaminhar qualquer header automaticamente.
Uma política comum é:
- aceitar
Authorizatione converter para metadata; - gerar ou propagar
X-Request-ID; - propagar contexto de tracing pelos padrões do OpenTelemetry;
- bloquear headers internos que o cliente não deveria definir;
- nunca registrar credenciais em logs.
Autenticação responde “quem é você?”; autorização responde “você pode acessar este usuário?”. O gateway pode validar token e aplicar políticas de borda, mas a autorização ligada ao recurso deve continuar protegida no serviço. Leia autenticação e autorização em APIs Go para estruturar essa separação.
OpenAPI gerada: útil, mas não automática demais
O plugin protoc-gen-openapiv2 gera um documento Swagger/OpenAPI v2 a partir do contrato. Isso acelera documentação, clientes e testes, mas o arquivo bruto raramente é suficiente para uma API pública.
Revise e enriqueça:
- título, descrição e versão;
- exemplos realistas de request e response;
- esquemas de autenticação;
- códigos de erro e seus corpos;
- paginação, filtros e limites;
- depreciação de campos e rotas;
- contato e política de compatibilidade.
Valide a especificação no CI e publique uma interface como Swagger UI ou Redoc apenas se ela estiver protegida conforme a sensibilidade do serviço. Para uma estratégia contract-first centrada em HTTP, compare com oapi-codegen em Go.
JSON, nomes de campos e valores ausentes
Protobuf e JSON não têm semântica idêntica. Antes de publicar o contrato, teste:
- nomes em
snake_caseno.protoe sua forma JSON; - enums desconhecidos;
- campos opcionais e presença;
- números inteiros grandes;
- timestamps e durations;
- bytes em base64;
- valores zero omitidos ou emitidos;
- campos desconhecidos enviados pelo cliente.
Você pode customizar marshaling com runtime.WithMarshalerOption, usando runtime.JSONPb e opções de protojson. Não altere opções por preferência estética sem avaliar compatibilidade. Emitir campos zero, por exemplo, muda o payload e pode afetar clientes, snapshots e cache.
Testes que realmente reduzem risco
Teste as três camadas, não apenas o método de domínio.
1. Teste do serviço gRPC
Chame o handler diretamente ou use bufconn para verificar serialização, interceptors, metadata e códigos gRPC.
2. Teste do gateway HTTP
Registre o mux gerado em httptest.NewServer e faça uma chamada JSON real:
func TestGetUserHTTP(t *testing.T) {
server := httptest.NewServer(gatewayHandler(t))
t.Cleanup(server.Close)
resp, err := http.Get(server.URL + "/v1/users/42")
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("status = %d", resp.StatusCode)
}
var got struct {
ID string `json:"id"`
Name string `json:"name"`
}
if err := json.NewDecoder(resp.Body).Decode(&got); err != nil {
t.Fatal(err)
}
if got.ID != "42" || got.Name == "" {
t.Fatalf("resposta inválida: %+v", got)
}
}
3. Teste de compatibilidade
Use buf breaking para detectar remoção de campos, troca de tipo e mudanças incompatíveis no contrato. Mantenha testes de snapshot do OpenAPI apenas quando o time revisa o diff conscientemente; snapshots aceitos no automático só registram a quebra.
Quando o serviço acessa PostgreSQL, Redis ou outra dependência, combine o teste de transporte com Testcontainers em Go.
Produção: timeouts, observabilidade e shutdown
O gateway adiciona uma fronteira, não uma licença para esquecer operação. Configure:
- timeouts no
http.Servere deadlines nas chamadas gRPC; - limites de corpo e tamanho de mensagem;
- CORS apenas para origens e métodos necessários;
- rate limit por identidade ou consumidor;
- logs estruturados sem payload sensível;
- métricas separando rota HTTP, método gRPC, status e latência;
- tracing entre gateway, serviço, banco e integrações;
- readiness para evitar tráfego antes das dependências estarem prontas;
- graceful shutdown para drenar HTTP e gRPC.
Evite usar IDs de usuário, e-mail ou URL completa como label de Prometheus. Prefira o template da rota, como /v1/users/{id}, para impedir cardinalidade sem limite. Os guias de health checks em Go e graceful shutdown cobrem essa base.
gRPC-Gateway vs REST manual vs ConnectRPC
| Critério | gRPC-Gateway | REST manual | ConnectRPC |
|---|---|---|---|
| Fonte principal | Protobuf | Código/OpenAPI | Protobuf |
| REST/JSON | Via transcodificação | Nativo e customizável | Protocolo HTTP amigável |
| gRPC | Sim | Só se implementar separado | Sim |
| OpenAPI | Plugin disponível | Ferramentas próprias | Fluxo depende da integração |
| Controle do payload HTTP | Médio | Máximo | Médio |
| Duplicação de handlers | Baixa | Pode ser alta | Baixa |
| Melhor encaixe | Ecossistema gRPC-Go existente | API pública HTTP-first | Serviços novos multi-protocolo |
Escolha gRPC-Gateway quando Protobuf já governa o contrato e clientes HTTP são uma interface adicional. Escolha REST manual quando HTTP é o produto principal e precisa de representação muito específica, caching, ETags, uploads ou semântica que não combina com o serviço RPC. Avalie ConnectRPC em projetos novos quando servir múltiplos protocolos pelo mesmo modelo de handler simplifica a arquitetura.
Não adote duas soluções ao mesmo tempo para “ficar preparado”. Escolha uma fronteira, documente o motivo e meça o custo operacional.
Armadilhas comuns
- Anotar qualquer RPC como REST sem redesenhar a rota. O resultado é uma API HTTP com cara de chamada de função.
- Expor o model interno inteiro. Contratos públicos precisam de estabilidade; não transforme toda coluna do banco em campo Protobuf.
- Executar sem deadline. Gateway e serviço podem acumular chamadas presas durante uma falha em cascata.
- Confiar só na documentação gerada. OpenAPI sem exemplos, erros e autenticação continua difícil de consumir.
- Propagar todos os headers. Isso cria colisões, risco de spoofing e vazamento de informação interna.
- Misturar validação de transporte e regra de negócio. Formato e presença básica podem ser validados na borda; autorização e invariantes pertencem ao serviço.
- Ignorar compatibilidade JSON. Uma mudança compatível em Protobuf pode surpreender um cliente que depende do payload JSON exato.
- Subir gateway e gRPC sem shutdown coordenado. O processo pode aceitar HTTP enquanto o backend já está encerrando.
Checklist antes do deploy
- Rotas usam recursos e métodos HTTP coerentes?
-
buf lintebuf breakingrodam no CI? - Plugins e geração são reproduzíveis?
- Códigos gRPC viram status HTTP corretos?
- O formato de erro é estável e não vaza detalhes?
- Headers permitidos estão em uma allowlist?
- Autenticação e autorização foram testadas separadamente?
- Timeouts existem no HTTP, gRPC e banco?
- OpenAPI contém exemplos e respostas de erro?
- Testes exercitam JSON real pelo gateway?
- Métricas usam template de rota, não IDs?
- Readiness e graceful shutdown cobrem os dois servidores?
- Existe estratégia de versionamento e depreciação?
Perguntas frequentes
O que é gRPC-Gateway em Go?
É um conjunto de plugins que gera um proxy HTTP/JSON a partir de serviços Protobuf. O gateway recebe REST, converte para a mensagem do método gRPC e traduz a resposta de volta para JSON.
Ele substitui uma API REST escrita à mão?
Sim, quando as operações HTTP representam naturalmente o mesmo contrato. Se a API pública exige payloads, recursos e políticas muito diferentes do RPC interno, uma camada REST própria oferece mais controle.
Preciso de dois servidores?
Não. Gateway e gRPC podem compartilhar um processo ou rodar separadamente. A primeira opção simplifica; a segunda permite escala e políticas de borda independentes.
gRPC-Gateway gera OpenAPI?
Sim, por meio do protoc-gen-openapiv2. Trate o resultado como ponto de partida e valide descrições, exemplos, segurança e respostas no CI.
Quando considerar ConnectRPC?
Considere ConnectRPC em serviços novos que precisam falar protocolos baseados em HTTP e gRPC com menos infraestrutura de proxy. Em uma plataforma já padronizada em gRPC-Go e google.api.http, gRPC-Gateway tende a exigir menos mudança.
Conclusão
O gRPC-Gateway resolve uma dor real de plataformas Go: oferecer uma interface HTTP/JSON sem duplicar contratos, DTOs e regras em dois conjuntos de handlers. A combinação funciona melhor quando Protobuf é a fonte de verdade, rotas REST são desenhadas com intenção e o time valida compatibilidade tanto no protocolo gRPC quanto no JSON publicado.
Comece pequeno: anote um método de leitura, gere o gateway com Buf, teste a rota com httptest e confirme erros, metadata e OpenAPI. Depois adicione autenticação, observabilidade, breaking-change detection e shutdown coordenado. Essa progressão entrega uma API útil sem transformar o gateway em uma segunda aplicação de negócio.
Para continuar, combine este guia com gRPC e Protobuf em produção, API REST com Go, OpenAPI com oapi-codegen, context e timeouts e as vagas Go no Brasil.