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 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 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;
import "buf/validate/validate.proto";
message GreetRequest {
string name = 1 [(buf.validate.field).string = {
min_len: 1,
max_len: 50,
}];
}
message GreetResponse {
string greeting = 1;
}
service GreetService {
rpc Greet(GreetRequest) returns (GreetResponse) {}
}
This file declares the greet.v1 Protobuf package, a service called GreetService, a single method called Greet with request and response messages, and includes validation for GreetRequest.
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 use protoc instead — protoc-gen-connect-go behaves like any other plugin.)
First, scaffold a basic buf.yaml by running buf config init and then replacing its contents with this:
version: v2
deps:
- buf.build/bufbuild/protovalidate
lint:
use:
- STANDARD
breaking:
use:
- FILE
Next, tell Buf how to generate code by putting this into
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
- simple
managed:
enabled: true
override:
- file_option: go_package_prefix
value: example/gen
disable:
- file_option: go_package
module: buf.build/bufbuild/protovalidate
With those configuration files in place, you can lint your schema and generate code:
$ buf dep update
$ 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 just over 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"
"net/http"
greetv1 "example/gen/greet/v1" // generated by protoc-gen-go
"example/gen/greet/v1/greetv1connect" // generated by protoc-gen-connect-go
"connectrpc.com/connect"
"connectrpc.com/validate"
)
type GreetServer struct{}
func (s *GreetServer) Greet(
_ context.Context,
req *greetv1.GreetRequest,
) (*greetv1.GreetResponse, error) {
res := &greetv1.GreetResponse{
Greeting: fmt.Sprintf("Hello, %s!", req.Name),
}
return res, nil
}
func main() {
greeter := &GreetServer{}
mux := http.NewServeMux()
path, handler := greetv1connect.NewGreetServiceHandler(
greeter,
// Validation via Protovalidate is almost always recommended
connect.WithInterceptors(validate.NewInterceptor()),
)
mux.Handle(path, handler)
p := new(http.Protocols)
p.SetHTTP1(true)
// Use h2c so we can serve HTTP/2 without TLS.
p.SetUnencryptedHTTP2(true)
s := http.Server{
Addr: "localhost:8080",
Handler: mux,
Protocols: p,
}
s.ListenAndServe()
}
In a separate terminal window, you can now update go.mod and start your
server:
$ go get connectrpc.com/connect
$ go get connectrpc.com/validate
$ go get buf.build/go/protovalidate
$ 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:
$ 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:
$ buf curl \
--schema ./greet/v1/greet.proto \
--protocol grpc \
--http2-prior-knowledge \
--data '{"name": "Jane"}' \
http://localhost:8080/greet.v1.GreetService/Greet
This responds:
{
"greeting": "Hello, Jane!"
}
Or, with the Connect protocol:
$ buf curl \
--schema . \
--data '{"name": "Jane"}' \
http://localhost:8080/greet.v1.GreetService/Greet
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"
)
func main() {
client := greetv1connect.NewGreetServiceClient(
http.DefaultClient,
"http://localhost:8080",
)
res, err := client.Greet(
context.Background(),
&greetv1.GreetRequest{Name: "Jane"},
)
if err != nil {
log.Println(err)
return
}
log.Println(res.Greeting)
}
With your server still running in a separate terminal window, you can now run your client:
$ go run ./cmd/client/main.go
Now try sending a request with no data:
$ buf curl \
--schema . \
http://localhost:8080/greet.v1.GreetService/Greet
It's rejected because you've included Protovalidate rules in your schema:
{
"code": "invalid_argument",
"message": "validation error:\n - name: value length must be at least 1 characters [string.min_len]"
}
Congratulations — you've built your first Connect service! 🎉
Use the gRPC protocol instead of the Connect protocol
connect-go supports three protocols:
- 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.
- The gRPC protocol that is used throughout the gRPC ecosystem, making
connect-gocompatible with other gRPC implementations out of the box.grpc-goclients will work withconnect-goservers 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-goservers to interop withgrpc-webfrontends without the need for an intermediary proxy (such as Envoy).
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.