Use Connect for incoming gRPC requests

The Connect protocol is an HTTP/2-based protocol for RPC communication. It's conceptually similar to gRPC, but with better support for using from browsers and JavaScript clients.

This guide shows how to use Encore for setting up a Connect service for external clients to use:

  1. First, we'll define a simple gRPC service using Protobuf and Connect.
  2. Then, we'll implement the service in Go, using connect-go.
  3. Then, we'll mount the Connect service into Encore with a raw endpoint.
  4. Finally, we'll call the Connect service from cURL using its JSON mapping.

Define a Connect service

We'll largely follow the connect-go getting started guide with some small tweaks.

Start by installing the necessary tools:

$ 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

Next, inside your Encore application (create one if you haven't already) create a new file at greet/v1/greet.proto with the following contents:

greet/v1/greet.proto
syntax = "proto3"; package greet.v1; option go_package = "encore.app/gen/greet/v1;greetv1"; message GreetRequest { string name = 1; } message GreetResponse { string greeting = 1; } service GreetService { rpc Greet(GreetRequest) returns (GreetResponse) {} }

Next, add a buf.gen.yaml in the repository root, containing:

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

Now it's time to generate the connect-go service code. Run:

$ buf lint
$ buf generate

If all went well, you should see a new gen directory in the repository root containing some generated Go code:

gen └── greet └── v1 ├── greet.pb.go └── greetv1connect └── greet.connect.go

Implement the service

Now that we have the service definition, we can implement the Connect service in Go.

Add the file greet/greet.go with the following contents:

greet/greet.go
package greet import ( "context" "fmt" "log" "connectrpc.com/connect" greetv1 "encore.app/gen/greet/v1" // generated by protoc-gen-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 }
Please note

The sample code is straight from the getting started guide; there are no Encore specific changes required here.

Mount the service in Encore

Now we'll create an Encore service struct that initializes the Connect service, and a raw endpoint that forwards incoming requests to the Connect service.

Add the file greet/service.go with the following contents:

greet/service.go
package greet import ( "net/http" "encore.app/gen/greet/v1/greetv1connect" "golang.org/x/net/http2" "golang.org/x/net/http2/h2c" ) //encore:service type Service struct { routes http.Handler } //encore:api public raw path=/greet.v1.GreetService/*endpoint func (s *Service) GreetService(w http.ResponseWriter, req *http.Request) { s.routes.ServeHTTP(w, req) } func initService() (*Service, error) { greeter := &GreetServer{} mux := http.NewServeMux() path, handler := greetv1connect.NewGreetServiceHandler(greeter) mux.Handle(path, handler) return &Service{routes: mux}, nil }

That's it! We're ready to run the service and check that everything works.

Run the service

Run the service with encore run:

$ encore run

Once it starts up, open a separate terminal and use grpcurl to call the service:

# Install grpcurl if you haven't already
$ go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest
# Call the service with grpcurl
grpcurl \
-protoset <(buf build -o -) -plaintext \
-d '{"name": "Jane"}' \
localhost:4000 greet.v1.GreetService/Greet
{"greeting": "Hello, Jane!"}
# Or call the service with curl
$ curl -H "Content-Type: application/json" -d '{"name": "Jane"}' http://localhost:4000/greet.v1.GreetService/Greet
{"greeting":"Hello, Jane!"} # Expected response

If you see {"greeting":"Hello, Jane!"}, everything is working!

What's more, Encore automatically traces the incoming requests, and adds request logging and captures request metrics.