---
title: "ConnectRPC em Go: API Protobuf sem a Complexidade do gRPC"
url: "https://golang.com.br/blog/connectrpc-go-api-protobuf-grpc-rest/"
markdown_url: "https://golang.com.br/blog/connectrpc-go-api-protobuf-grpc-rest.MD"
description: "Aprenda ConnectRPC em Go com Protobuf, Buf e net/http: gere cliente e servidor tipados, teste com curl e entenda quando usar Connect, gRPC ou REST."
date: "2026-09-03"
author: "Golang Brasil"
---

# ConnectRPC em Go: API Protobuf sem a Complexidade do gRPC

Aprenda ConnectRPC em Go com Protobuf, Buf e net/http: gere cliente e servidor tipados, teste com curl e entenda quando usar Connect, gRPC ou REST.


**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](/blog/grpc-go-protobuf-apis-internas/). Quando também for necessário publicar uma interface REST, compare com [gRPC-Gateway em Go](/blog/grpc-gateway-go-rest-protobuf/).

## 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:

```text
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:

```bash
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 é:

```bash
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`:

```bash
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`:

```protobuf
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:

```protobuf
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`:

```yaml
version: v2
modules:
  - path: proto
lint:
  use:
    - STANDARD
breaking:
  use:
    - FILE
```

Depois crie `buf.gen.yaml`:

```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:

```bash
buf lint
buf generate
```

O projeto passa a ter uma estrutura semelhante a esta:

```text
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`:

```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:

```go
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`:

```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:

```bash
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](/blog/api-go-producao-checklist/) reúne essas verificações, e o guia de [middleware em Go](/blog/middleware-go-net-http-chi-gin-producao/) 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:

```bash
curl \
  --header 'Content-Type: application/json' \
  --data '{"id":"go-101"}' \
  http://localhost:8080/catalog.v1.CatalogService/GetProduct
```

Resposta esperada:

```json
{
  "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:

```go
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](/blog/context-timeout-cancelamento-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](/blog/go-opentelemetry-observabilidade-tracing-metricas/) e [logging estruturado com slog](/blog/slog-go-logging-estruturado/).

## Compatibilidade e evolução do contrato

Protobuf facilita evolução, mas não torna toda mudança segura. Boas práticas:

1. adicione campos novos com números inéditos;
2. não altere o significado de um campo existente;
3. não troque tipos sem entender a compatibilidade wire e semântica;
4. reserve nomes e números removidos;
5. mantenha servidor e clientes compatíveis durante o rollout;
6. execute verificação de breaking changes no CI;
7. versione o pacote quando houver uma mudança realmente incompatível.

Com Buf, o CI pode comparar a branch com a referência principal:

```bash
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 `.proto` passam 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](/blog/grpc-go-protobuf-apis-internas/)
- [gRPC-Gateway: REST e Protobuf no mesmo contrato](/blog/grpc-gateway-go-rest-protobuf/)
- [Checklist de API Go em produção](/blog/api-go-producao-checklist/)
- [OpenTelemetry em Go](/blog/go-opentelemetry-observabilidade-tracing-metricas/)
- [Como escolher um framework HTTP em Go](/blog/frameworks-http-go-gin-echo-fiber-chi/)
- [Vagas de Go abertas no Brasil](/vagas/)

## Referências oficiais

- [Documentação do Connect](https://connectrpc.com/docs/go/getting-started/)
- [Repositório connect-go](https://github.com/connectrpc/connect-go)
- [Documentação do Buf](https://buf.build/docs/)
- [Protocol Buffers: guia da linguagem proto3](https://protobuf.dev/programming-guides/proto3/)
- [Protocol Buffers: práticas para atualização de mensagens](https://protobuf.dev/best-practices/dos-donts/)
