Quickly Building Browser-Compatible gRPC Services

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…