Resposta rápida: use ConnectRPC em Go quando você quer uma API tipada com Protobuf, geração de clientes e compatibilidade com gRPC, mas prefere operar sobre o net/http da biblioteca padrão e manter chamadas unary fáceis de testar com HTTP e JSON. O servidor pode atender o protocolo Connect e, quando necessário, clientes gRPC e gRPC-Web. Para uma API pública orientada a recursos, REST ainda pode ser mais simples; para comunicação interna entre serviços e SDKs controlados pelo time, ConnectRPC é uma opção forte.
ConnectRPC não é “gRPC sem Protobuf” nem apenas outro framework HTTP. O contrato continua definido em arquivos .proto, e o código de cliente e servidor é gerado. A diferença prática está na integração direta com HTTP, na interoperabilidade entre protocolos e em uma experiência de desenvolvimento que reduz parte do atrito comum de gRPC.
Neste guia, você vai criar uma API de catálogo com Go, ConnectRPC e Buf, chamá-la com curl, escrever um cliente tipado e entender os trade-offs entre Connect, gRPC e REST.
ConnectRPC, gRPC ou REST: comparação rápida
| Critério | ConnectRPC | gRPC tradicional | REST com JSON |
|---|---|---|---|
| Contrato | Protobuf | Protobuf | OpenAPI opcional |
| Transporte comum | HTTP/1.1 ou HTTP/2 | HTTP/2 | HTTP/1.1 ou HTTP/2 |
| Cliente tipado gerado | Sim | Sim | Depende do OpenAPI |
Chamada unary com curl e JSON | Sim | Exige ferramenta ou gateway | Sim |
| Streaming | Suportado, com diferenças por protocolo | Forte suporte | Normalmente SSE, WebSocket ou solução própria |
| Navegador | Connect e gRPC-Web | Precisa de gRPC-Web/proxy | Nativo |
| Integração Go | net/http | Runtime próprio de gRPC | net/http ou framework |
| Melhor encaixe | APIs internas e SDKs controlados | Ecossistema gRPC consolidado | APIs públicas e recursos HTTP |
A tabela não define um vencedor universal. Se a empresa já opera service mesh, interceptors, observabilidade e clientes gRPC em várias linguagens, trocar a implementação apenas por novidade gera pouco valor. Se um projeto novo quer Protobuf sem abrir mão de ferramentas HTTP familiares, ConnectRPC merece um protótipo.
Para revisar a alternativa consolidada, leia gRPC com Go e Protobuf. Quando também for necessário publicar uma interface REST, compare com gRPC-Gateway em Go.
Como o ConnectRPC funciona
Você descreve serviços e mensagens em Protobuf. Plugins geram:
- tipos Go para as mensagens;
- uma interface que o servidor implementa;
- um handler HTTP para registrar no mux;
- um cliente tipado para chamar o serviço.
O fluxo é este:
catalog.proto
↓ geração
catalog.pb.go tipos Protobuf
catalog.connect.go cliente, interface e handler Connect
↓
implementação Go → net/http → clientes Connect, gRPC ou gRPC-Web
A regra arquitetural importante é separar o contrato gerado da regra de negócio escrita à mão. Você pode regenerar os arquivos sem apagar sua implementação, e mudanças incompatíveis ficam visíveis na revisão do .proto.
Preparando o projeto
Crie o módulo:
mkdir catalog-api
cd catalog-api
go mod init exemplo.com/catalog-api
go get connectrpc.com/connect
go get google.golang.org/protobuf
Instale a CLI do Buf e os plugins de geração conforme a documentação oficial do projeto. Uma forma comum no ambiente Go é:
go install github.com/bufbuild/buf/cmd/buf@latest
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install connectrpc.com/connect/cmd/protoc-gen-connect-go@latest
Confirme que o diretório de binários do Go está no PATH:
export PATH="$(go env GOPATH)/bin:$PATH"
buf --version
protoc-gen-go --version
protoc-gen-connect-go --version
Em CI, fixe versões em vez de depender permanentemente de @latest. O objetivo é fazer a mesma revisão gerar o mesmo código em todas as máquinas.
Definindo o serviço Protobuf
Crie proto/catalog/v1/catalog.proto:
syntax = "proto3";
package catalog.v1;
option go_package = "exemplo.com/catalog-api/gen/catalog/v1;catalogv1";
service CatalogService {
rpc GetProduct(GetProductRequest) returns (GetProductResponse) {}
}
message GetProductRequest {
string id = 1;
}
message GetProductResponse {
Product product = 1;
}
message Product {
string id = 1;
string name = 2;
int64 price_cents = 3;
}
O nome catalog.v1 cria espaço para evolução do contrato. O campo go_package define o import usado pelo código Go gerado. Em um repositório real, substitua exemplo.com/catalog-api pelo caminho do seu módulo.
Não reutilize números de campos removidos. Em Protobuf, o número faz parte do formato serializado. Ao excluir um campo publicado, marque seu nome e número como reserved para impedir reutilização acidental:
message Product {
reserved 4;
reserved "legacy_sku";
string id = 1;
string name = 2;
int64 price_cents = 3;
}
Configurando o Buf
Na raiz, crie buf.yaml:
version: v2
modules:
- path: proto
lint:
use:
- STANDARD
breaking:
use:
- FILE
Depois crie buf.gen.yaml:
version: v2
plugins:
- local: protoc-gen-go
out: gen
opt: paths=source_relative
- local: protoc-gen-connect-go
out: gen
opt: paths=source_relative
Gere os arquivos:
buf lint
buf generate
O projeto passa a ter uma estrutura semelhante a esta:
catalog-api/
├── cmd/server/main.go
├── gen/catalog/v1/catalog.pb.go
├── gen/catalog/v1/catalogv1connect/catalog.connect.go
├── internal/catalog/service.go
├── proto/catalog/v1/catalog.proto
├── buf.gen.yaml
├── buf.yaml
└── go.mod
Os caminhos exatos seguem o pacote e as opções configuradas. Não edite os arquivos em gen/ manualmente: altere o .proto ou a configuração e gere novamente.
Implementando o servidor ConnectRPC
Crie internal/catalog/service.go:
package catalog
import (
"context"
"errors"
"connectrpc.com/connect"
catalogv1 "exemplo.com/catalog-api/gen/catalog/v1"
)
type Service struct{}
func (s *Service) GetProduct(
ctx context.Context,
req *connect.Request[catalogv1.GetProductRequest],
) (*connect.Response[catalogv1.GetProductResponse], error) {
if req.Msg.GetId() == "" {
return nil, connect.NewError(
connect.CodeInvalidArgument,
errors.New("id do produto é obrigatório"),
)
}
if req.Msg.GetId() != "go-101" {
return nil, connect.NewError(
connect.CodeNotFound,
errors.New("produto não encontrado"),
)
}
response := connect.NewResponse(&catalogv1.GetProductResponse{
Product: &catalogv1.Product{
Id: "go-101",
Name: "Go para Back-end",
PriceCents: 12990,
},
})
response.Header().Set("Catalog-Version", "2026-09")
return response, nil
}
Em produção, não devolva erros sem contexto interno para os logs. O cliente deve receber um código estável e uma mensagem segura; detalhes de banco, stack trace e identificadores sensíveis ficam na observabilidade do servidor.
Você também pode usar connect.NewError com um erro descritivo seguro:
return nil, connect.NewError(
connect.CodeInvalidArgument,
fmt.Errorf("id do produto é obrigatório"),
)
Os códigos Connect correspondem ao modelo conhecido de gRPC, como invalid_argument, not_found, already_exists, permission_denied, unavailable e internal. Não devolva internal para todo caso: o código faz parte do contrato operacional de retry e tratamento no cliente.
Publicando com net/http
Crie cmd/server/main.go:
package main
import (
"log"
"net/http"
"time"
catalogv1connect "exemplo.com/catalog-api/gen/catalog/v1/catalogv1connect"
"exemplo.com/catalog-api/internal/catalog"
)
func main() {
mux := http.NewServeMux()
path, handler := catalogv1connect.NewCatalogServiceHandler(
&catalog.Service{},
)
mux.Handle(path, handler)
server := &http.Server{
Addr: ":8080",
Handler: mux,
ReadHeaderTimeout: 5 * time.Second,
IdleTimeout: 60 * time.Second,
}
log.Printf("API ConnectRPC ouvindo em %s", server.Addr)
log.Fatal(server.ListenAndServe())
}
Execute:
go run ./cmd/server
O handler gerado informa o path correto do serviço e traduz requisições HTTP para a interface implementada. Como ele é um http.Handler, middlewares compatíveis com net/http podem envolver o serviço para logging, autenticação, métricas e recuperação de panic.
Antes de publicar, adicione timeouts, graceful shutdown e health checks. O checklist de API Go em produção reúne essas verificações, e o guia de middleware em Go ajuda a evitar uma pilha de wrappers sem ordem definida.
Testando com curl
Uma vantagem do protocolo Connect para métodos unary é a chamada direta com JSON. Com o servidor local, execute:
curl \
--header 'Content-Type: application/json' \
--data '{"id":"go-101"}' \
http://localhost:8080/catalog.v1.CatalogService/GetProduct
Resposta esperada:
{
"product": {
"id": "go-101",
"name": "Go para Back-end",
"priceCents": "12990"
}
}
O int64 pode aparecer como string no mapeamento JSON de Protobuf para preservar precisão em ambientes como JavaScript. Não altere o contrato apenas para deixar uma demonstração mais bonita; valide como os clientes reais serializam e desserializam esses valores.
Para um ID inexistente, o servidor deve devolver um erro de protocolo com código not_found. Testar com curl é excelente para diagnóstico, mas não substitui testes automatizados nem o cliente gerado.
Criando um cliente Go tipado
Um cliente Go usa o mesmo contrato:
package main
import (
"context"
"fmt"
"net/http"
"time"
"connectrpc.com/connect"
catalogv1 "exemplo.com/catalog-api/gen/catalog/v1"
catalogv1connect "exemplo.com/catalog-api/gen/catalog/v1/catalogv1connect"
)
func main() {
httpClient := &http.Client{
Timeout: 3 * time.Second,
}
client := catalogv1connect.NewCatalogServiceClient(
httpClient,
"http://localhost:8080",
)
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
response, err := client.GetProduct(
ctx,
connect.NewRequest(&catalogv1.GetProductRequest{Id: "go-101"}),
)
if err != nil {
fmt.Printf("erro: %v\n", err)
return
}
fmt.Printf("%s: R$ %.2f\n",
response.Msg.Product.Name,
float64(response.Msg.Product.PriceCents)/100,
)
}
O cliente recebe um http.Client, então você controla timeout, proxy, TLS e transporte. Reutilize a instância para aproveitar conexões; não crie um cliente novo a cada chamada.
Propague context.Context com um prazo coerente com o orçamento total da requisição. O guia de context, timeout e cancelamento em Go mostra como evitar operações que continuam rodando depois que o cliente desistiu.
Interceptors para autenticação e observabilidade
Interceptors cumprem um papel semelhante ao middleware: envolvem chamadas para aplicar comportamento transversal. Casos comuns incluem:
- validar token ou identidade mTLS;
- adicionar request ID;
- registrar duração e código do procedimento;
- criar spans do OpenTelemetry;
- coletar métricas por serviço e método;
- aplicar política de retry no cliente.
Evite colocar regra de autorização inteira em um interceptor global. Autenticação pode ser transversal, mas autorização por recurso normalmente depende do objeto carregado e deve permanecer próxima do caso de uso.
Também cuide da cardinalidade. Uma métrica com product_id, user_id ou mensagem de erro como label pode explodir a quantidade de séries. Prefira serviço, procedimento e código de status. IDs específicos ficam em logs ou traces, conforme sua política de privacidade.
Para instrumentação distribuída, consulte OpenTelemetry em Go e logging estruturado com slog.
Compatibilidade e evolução do contrato
Protobuf facilita evolução, mas não torna toda mudança segura. Boas práticas:
- adicione campos novos com números inéditos;
- não altere o significado de um campo existente;
- não troque tipos sem entender a compatibilidade wire e semântica;
- reserve nomes e números removidos;
- mantenha servidor e clientes compatíveis durante o rollout;
- execute verificação de breaking changes no CI;
- versione o pacote quando houver uma mudança realmente incompatível.
Com Buf, o CI pode comparar a branch com a referência principal:
buf lint
buf breaking --against '.git#branch=main'
buf generate
git diff --exit-code
O último comando detecta quando alguém alterou o .proto, mas esqueceu de commitar o código gerado. Algumas equipes preferem gerar apenas no build; outras versionam os arquivos para facilitar consumo e revisão. Escolha uma política e automatize-a.
Streaming exige atenção ao protocolo e ao proxy
ConnectRPC suporta streaming, mas a compatibilidade varia conforme protocolo, transporte, navegador e infraestrutura. Antes de desenhar um serviço de eventos, valide:
- se o cliente usa Connect, gRPC ou gRPC-Web;
- se a chamada precisa ser client streaming, server streaming ou bidirecional;
- se o proxy mantém HTTP/2 e não aplica buffering inadequado;
- quais timeouts existem no load balancer;
- como cancelamento e backpressure são propagados;
- como o fluxo será observado e limitado.
Para notificações simples ao navegador, WebSocket ou Server-Sent Events pode ser mais fácil de operar. Para comunicação interna já padronizada em gRPC, streaming RPC pode ser natural. Não escolha o protocolo apenas pelo exemplo mais curto.
Segurança antes do deploy
Uma API tipada ainda precisa das mesmas proteções de qualquer serviço:
- TLS entre cliente e servidor;
- autenticação e autorização explícitas;
- limites de tamanho de mensagem;
- deadlines no cliente e no servidor;
- rate limiting nos procedimentos caros;
- erros públicos sem dados internos;
- validação semântica além do tipo Protobuf;
- proteção contra overload e filas ilimitadas;
- atualização e verificação de dependências.
Protobuf garante que price_cents seja um inteiro; ele não garante que o preço seja positivo, que o usuário possa acessar o produto ou que um ID pertença ao tenant autenticado.
Se o serviço estiver atrás de CORS para uso no navegador, configure origens e headers de forma restrita. gRPC-Web e Connect no browser não removem o modelo de segurança do navegador.
Quando ConnectRPC é a escolha certa
Considere ConnectRPC quando:
- serviços internos compartilham contratos Protobuf;
- você controla clientes e servidor;
- SDKs gerados reduzem duplicação e erros;
- existe necessidade de interoperar com gRPC;
- o time quer usar
net/http, middlewares e infraestrutura HTTP conhecida; - chamadas unary com JSON facilitam debugging;
- evolução de schema e breaking changes precisam de governança.
Prefira REST quando:
- a API será consumida por parceiros sem toolchain compartilhado;
- URLs, métodos e recursos HTTP são parte importante do produto;
- cache HTTP e exploração manual têm alto valor;
- OpenAPI já é o padrão da organização;
- geração obrigatória de cliente criaria mais atrito que segurança.
Prefira gRPC tradicional quando:
- a plataforma já está consolidada em gRPC;
- bibliotecas, proxies e observabilidade existentes dependem da implementação atual;
- streaming avançado é central e já foi validado na infraestrutura;
- mudar não resolve um problema operacional concreto.
Uma arquitetura pode combinar abordagens: ConnectRPC para comunicação interna tipada, REST para integrações públicas e eventos para processamento assíncrono. O risco aparece quando cada equipe inventa um protocolo diferente sem padrões de autenticação, erro, observabilidade e versionamento.
Checklist de produção para ConnectRPC em Go
Antes do deploy, confirme:
- arquivos
.protopassam por lint e breaking check; - geração é reproduzível e versões dos plugins estão fixadas;
- cliente e servidor têm deadlines;
- servidor implementa graceful shutdown;
- autenticação e autorização foram testadas por procedimento;
- limites de mensagem e concorrência estão definidos;
- erros Connect usam códigos coerentes;
- logs não armazenam tokens nem payloads sensíveis;
- métricas evitam labels de alta cardinalidade;
- traces propagam contexto entre serviços;
- proxy e load balancer suportam o protocolo e streaming necessários;
- health checks não dependem de todas as integrações externas;
- rollout mantém compatibilidade com clientes antigos;
Conclusão
ConnectRPC em Go oferece um caminho pragmático para APIs RPC tipadas: contrato Protobuf, clientes gerados, compatibilidade com o ecossistema gRPC e integração com o servidor HTTP padrão. Sua melhor característica não é economizar algumas linhas, mas permitir que contrato, transporte e implementação evoluam com menos improvisação.
Comece com um método unary pequeno, gere um cliente Go e teste a chamada por HTTP. Depois valide autenticação, observabilidade, breaking changes e comportamento atrás do proxy real. Se essa experiência reduzir atrito sem comprometer requisitos de interoperabilidade, ConnectRPC pode ser uma base melhor que montar manualmente uma API interna ou adotar toda a complexidade de uma plataforma gRPC sem necessidade.
Próximos passos
- gRPC com Go e Protobuf para APIs internas
- gRPC-Gateway: REST e Protobuf no mesmo contrato
- Checklist de API Go em produção
- OpenTelemetry em Go
- Como escolher um framework HTTP em Go
- Vagas de Go abertas no Brasil