What's new in version 2
connect-go v2 is a new major version of the Go API, not of ConnectRPC. The wire protocol, gRPC compatibility, and the rest of the ecosystem are unchanged. v1 remains supported on the v1 branch and the two versions can be used in the same module.
Generated code takes and returns plain messages. The connect.Request[T] and connect.Response[T] wrappers are gone, so the simple option is now the only shape:
-func (s *PingServer) Ping(ctx context.Context, req *connect.Request[pingv1.PingRequest]) (*connect.Response[pingv1.PingResponse], error) {
- return connect.NewResponse(&pingv1.PingResponse{Number: req.Msg.Number}), nil
+func (s *PingServer) Ping(ctx context.Context, req *pingv1.PingRequest) (*pingv1.PingResponse, error) {
+ return &pingv1.PingResponse{Number: req.Number}, nil
}The core is now transport-agnostic. Generated code talks to a small Transport interface instead of net/http. connecthttp serves and calls Connect, gRPC, and gRPC-Web over net/http, and connectinprocess calls a server directly in the same process.
Servers register on a *connect.Server and clients are built from a transport:
server := connect.NewServer(validate.NewServerInterceptor())
pingv1connect.RegisterPingServiceHandler(server, &PingServer{})
connecthttp.Mount(mux, server)
client := connect.NewClient(connecthttp.NewTransport(http.DefaultClient, "http://localhost:8080"))
pingClient := pingv1connect.NewPingServiceClient(client)
res, err := pingClient.Ping(ctx, &pingv1.PingRequest{Number: 42})Test without a network
connectinprocess connects a client to a server in the same process. Tests no longer need httptest.Server or a loopback listener:
server := connect.NewServer()
pingv1connect.RegisterPingServiceHandler(server, &PingServer{})
client := pingv1connect.NewPingServiceClient(connect.NewClient(connectinprocess.New(server)))Interceptors wrap the whole call
Interceptors run before any decompression or unmarshaling. An auth check can reject a call from its headers alone, without separate HTTP middleware:
func authInterceptor(next connect.ServerFunc) connect.ServerFunc {
return func(ctx context.Context, spec connect.Spec, stream connect.ServerStream) error {
info, _ := connect.CallInfoForServerContext(ctx)
if info.RequestHeader().Get("Authorization") == "" {
return connect.NewError(connect.CodeUnauthenticated, "missing credentials")
}
return next(ctx, spec, stream)
}
}Errors don't leak by accident
Only a *connect.Error you create sends its message to the client. Any other error, including one returned from a downstream RPC, reaches the client as a bare code with no message. Attach the original error with WithCause to keep it for errors.Is and errors.As on the server:
if err := store(ctx, req.Number); err != nil {
return nil, connect.NewError(connect.CodeInternal, "store failed").WithCause(err)
}Smaller binaries and a smaller API
Without generic client instantiation, the compiler no longer emits a client per RPC. Moving the Buf CLI to v2 shrinks its binary by about 10%. The core package exports about 25 identifiers, down from about 180 in v1, and stream types are interfaces that can be faked in tests.
The v2 guide explains what changed and why.
Tip
If you already use connect-go, see the migration guide. The connect-go-v2-migrate tool updates buf.gen.yaml and rewrites most of your code.
To upgrade, run:
go install connectrpc.com/connect/v2/cmd/connect-go-v2-migrate@latest
connect-go-v2-migrate -w
buf generate
connect-go-v2-migrate -w
go mod tidyv1 is still supported
v1 remains stable and supported indefinitely. It lives on the v1 branch and will keep receiving bug fixes and security patches as connectrpc.com/connect v1.x releases. There is no deadline to migrate. v1 and v2 have different module paths, so both can be used in the same module and services can move one at a time.
Full Changelog: v1.21.0...v2.0.0