Introduction

Although gRPC has become quite mature, compatibility issues still exist in the frontend domain. Currently, there are two solutions: one is to use RESTful API services in the backend to wrap gRPC services, and the other is to directly use projects like gRPC-Web in the frontend to enable direct calls to gRPC!

To make projects support both RESTful API and gRPC, people created the gRPC-Gateway project, which mainly solves the problem of RESTful reverse proxy routing for gRPC services.

Regardless of the method, there is a certain cognitive burden for developers who are just getting started. Later, in an open-source project, I discovered that they were using Buf’s open-source ConnectRPC project to implement this layer of compatibility.

ConnectRPC’s slogan is:

Connect is a family of libraries for building browser and gRPC-compatible APIs. If you’re tired of hand-written boilerplate and turned off by massive frameworks, Connect is for you.

Installing Required Tools

mkdir example
cd 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

Defining Services

Create a greet/v1/greet.proto file with the following content:

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) {}
}

Generating Code

First, generate a buf.yaml for the project:

version: v2
plugins:
  - local: protoc-gen-go
    out: gen
    opt: paths=source_relative
  - local: protoc-gen-connect-go
    out: gen
    opt: paths=source_relative

Then run the buf generate command to generate code:

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

Implementing Business Logic

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)

    // Use h2c so we can serve HTTP/2 without TLS.
    http.ListenAndServe("localhost:8080", h2c.NewHandler(mux, &http2.Server{}))
}

Installing Dependencies and Running

go get golang.org/x/net/http2
go get connectrpc.com/connect
go run ./cmd/server/main.go

Sending Requests

Here’s a RESTful JSON request:

curl \
    --header "Content-Type: application/json" \
    --data '{"name": "Jane"}' \
    http://localhost:8080/greet.v1.GreetService/Greet

Here’s a gRPC Protobuf request:

grpcurl \
    -protoset <(buf build -o -) -plaintext \
    -d '{"name": "Jane"}' \
    localhost:8080 greet.v1.GreetService/Greet

Conclusion

At this point, we’ve basically completed the compatibility between RESTful API and gRPC, but the default routing goes against RESTful API specifications. To make it conform to RESTful API standards, you’ll need to use the connectrpc/vanguard-go library.

Vanguard is a powerful Go net/http server library that enables seamless transcoding between REST and RPC protocols. Whether you need to bridge the gap between gRPC, gRPC-Web, Connect, or REST, Vanguard has you covered. With support for Google’s HTTP transcoding options, it can transform protocols using strongly-typed Protobuf definitions with ease.

Unlike traditional approaches like gRPC-Gateway, Vanguard runs efficiently within Go servers and is compatible with various servers like Connect and gRPC. It doesn’t rely on extensive code generation, eliminating the need for additional code generation steps. This flexibility ensures your code can adapt dynamically, loading service definitions from configuration, schema registries, or via gRPC server reflection, making it perfect for proxies without requiring recompilation and redeployment every time an RPC service schema changes.

I hope this is helpful, Happy hacking…