Skip to main content

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

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 with connect-go servers and vice versa without issue - in fact, before connect-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 with grpc-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.