Getting started
Connect is a slim library for building browser- and gRPC-compatible HTTP APIs. You define your service with a Protocol Buffer schema, and Connect generates type-safe server and client code. Fill in your server's business logic and you're done — no hand-written marshaling, routing, or client code required!
This fifteen-minute walkthrough helps you create a small Connect service in Go. It demonstrates what you'll be writing by hand, what Connect generates for you, and how to call your new API.
Prerequisites
- You'll need one of the last two major releases of Go. See Go's Getting Started guide for installation instructions.
- We'll also use cURL. It's available from Homebrew and most Linux package managers.
Install tools
First, we'll need to create a new Go module and install some code generation tools:
$ mkdir connect-go-example
$ cd connect-go-example
$ go mod init example
$ go install github.com/bufbuild/buf/cmd/buf@latest
$ go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest
$ go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
$ go install connectrpc.com/connect/cmd/protoc-gen-connect-go@latest
You'll need buf
, protoc-gen-go
and protoc-gen-connect-go
on your PATH
. If
which buf grpcurl protoc-gen-go protoc-gen-connect-go
doesn't succeed, add Go's
install directories to your path:
$ [ -n "$(go env GOBIN)" ] && export PATH="$(go env GOBIN):${PATH}"
$ [ -n "$(go env GOPATH)" ] && export PATH="$(go env GOPATH)/bin:${PATH}"
Define a service
Now we're ready to write the Protocol Buffer schema that defines our service. In your shell,
$ mkdir -p greet/v1
$ touch greet/v1/greet.proto
Open greet/v1/greet.proto
in your editor and add:
syntax = "proto3";
package greet.v1;
option go_package = "example/gen/greet/v1;greetv1";
message GreetRequest {
string name = 1;
}
message GreetResponse {
string greeting = 1;
}
service GreetService {
rpc Greet(GreetRequest) returns (GreetResponse) {}
}
This file declares the greet.v1
Protobuf package, a service called
GreetService
, and a single method called Greet
with its request and
response structures. These package, service, and method names will reappear
soon in our HTTP API's URLs.
Generate code
We're going to generate our code using Buf, a modern replacement for
Google's protobuf compiler. We installed Buf earlier, but we also need a few
configuration files to get going. (If you'd prefer, you can skip this section
and use protoc
instead — protoc-gen-connect-go
behaves like any other
plugin.)
First, scaffold a basic buf.yaml
by running buf mod init
. Next,
tell Buf how to generate code by putting this into
buf.gen.yaml
:
version: v1
plugins:
- plugin: go
out: gen
opt: paths=source_relative
- plugin: connect-go
out: gen
opt: paths=source_relative
With those configuration files in place, you can lint your schema and generate code:
$ buf lint
$ buf generate
In your gen
directory, you should now see some generated Go:
gen
└── greet
└── v1
├── greet.pb.go
└── greetv1connect
└── greet.connect.go
The package gen/greet/v1
contains greet.pb.go
, which was generated by
Google's protoc-gen-go
, and it contains the GreetRequest
and GreetResponse
structs and the associated marshaling code. The package
gen/greet/v1/greetv1connect
contains greet.connect.go
, which was generated
by protoc-gen-connect-go
, and it contains the HTTP handler and client
interfaces and constructors. Feel free to poke around if you're interested
— greet.connect.go
is less than 100 lines of code, including comments.
Implement handler
The code we've generated takes care of the boring boilerplate, but we still
need to implement our greeting logic. In the generated code, this is
represented as the greetv1connect.GreetServiceHandler
interface. Since the
interface is so small, we can do everything in one Go package. mkdir -p
cmd/server
, then add cmd/server/main.go
:
package main
import (
"context"
"fmt"
"log"
"net/http"
"connectrpc.com/connect"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
greetv1 "example/gen/greet/v1" // generated by protoc-gen-go
"example/gen/greet/v1/greetv1connect" // generated by protoc-gen-connect-go
)
type GreetServer struct{}
func (s *GreetServer) Greet(
ctx context.Context,
req *connect.Request[greetv1.GreetRequest],
) (*connect.Response[greetv1.GreetResponse], error) {
log.Println("Request headers: ", req.Header())
res := connect.NewResponse(&greetv1.GreetResponse{
Greeting: fmt.Sprintf("Hello, %s!", req.Msg.Name),
})
res.Header().Set("Greet-Version", "v1")
return res, nil
}
func main() {
greeter := &GreetServer{}
mux := http.NewServeMux()
path, handler := greetv1connect.NewGreetServiceHandler(greeter)
mux.Handle(path, handler)
http.ListenAndServe(
"localhost:8080",
// Use h2c so we can serve HTTP/2 without TLS.
h2c.NewHandler(mux, &http2.Server{}),
)
}
As you've probably noticed, the Greet
method uses generics: the
connect.Request
and connect.Response
types offer direct access to headers
and trailers, while still providing strongly-typed access to the generated
greetv1.GreetRequest
and greetv1.GreetResponse
structs. Generics simplify
many portions of Connect, and even let advanced users skip using
protoc-gen-connect-go
.
In a separate terminal window, you can now update go.mod
and start your
server:
$ go get golang.org/x/net/http2
$ go get connectrpc.com/connect
$ go run ./cmd/server/main.go
Make requests
The simplest way to consume your new API is an HTTP/1.1 POST with a JSON payload. If you have a recent version of cURL installed, it's a one-liner:
$ curl \
--header "Content-Type: application/json" \
--data '{"name": "Jane"}' \
http://localhost:8080/greet.v1.GreetService/Greet
This responds:
{"greeting": "Hello, Jane!"}
Your new handler automatically supports gRPC requests, too:
$ grpcurl \
-protoset <(buf build -o -) -plaintext \
-d '{"name": "Jane"}' \
localhost:8080 greet.v1.GreetService/Greet
This responds:
{
"greeting": "Hello, Jane!"
}
We can also make requests using Connect's generated client. mkdir -p
cmd/client
and put this in cmd/client/main.go
:
package main
import (
"context"
"log"
"net/http"
greetv1 "example/gen/greet/v1"
"example/gen/greet/v1/greetv1connect"
"connectrpc.com/connect"
)
func main() {
client := greetv1connect.NewGreetServiceClient(
http.DefaultClient,
"http://localhost:8080",
)
res, err := client.Greet(
context.Background(),
connect.NewRequest(&greetv1.GreetRequest{Name: "Jane"}),
)
if err != nil {
log.Println(err)
return
}
log.Println(res.Msg.Greeting)
}
With your server still running in a separate terminal window, you can now run your client:
$ go run ./cmd/client/main.go
Congratulations — you've built your first Connect service! 🎉
Use the gRPC protocol instead of the Connect protocol
connect-go
supports three protocols:
- The gRPC protocol that is used throughout the gRPC ecosystem, making
connect-go
compatible with other gRPC implementations out of the box.grpc-go
clients will work withconnect-go
servers and vice versa without issue - in fact, beforeconnect-go
's public release, this is exactly what the Buf CLI was doing. - The gRPC-Web protocol used by gRPC/gRPC-Web, allowing
connect-go
servers to interop withgrpc-web
frontends without the need for an intermediary proxy (such as Envoy). - The new Connect protocol, a simple, HTTP-based protocol that works over HTTP/1.1 or HTTP/2. It takes the best portions of gRPC and gRPC-Web, including streaming, and packages them into a protocol that works equally well in browsers, monoliths, and microservices. The Connect protocol is what we think the gRPC protocol should be. By default, JSON- and binary-encoded Protobuf is supported.
By default, connect-go
servers support ingress from all three protocols
without any configuration. connect-go
clients use the Connect protocol by
default, but can use either the gRPC or gRPC-Web protocols by setting
the WithGRPC
or WithGRPCWeb
client options.
Edit cmd/client/main.go
above to create the GreetServiceClient
using the
WithGRPC
option:
client := greetv1connect.NewGreetServiceClient(
http.DefaultClient,
"http://localhost:8080",
connect.WithGRPC(),
)
With your server still running in a separate terminal window, run the client one more time:
$ go run ./cmd/client/main.go
Your output should remain the same, but connect-go
is now using the gRPC
protocol instead of the Connect protocol to communicate over the wire.
So what?
With just a few lines of hand-written code, you've built a real API server that supports both the gRPC and Connect protocols. Unlike a hand-written REST service, you didn't need to design a URL hierarchy, hand-write request and response structs, manage your own marshaling, or parse typed values out of query parameters. More importantly, your users got an idiomatic, type-safe client without any extra work on your part.