Exposing gRPC services as REST APIs with grpc-gateway is surprisingly straightforward because it essentially translates RESTful HTTP requests into gRPC calls and vice-versa, without requiring you to write any gRPC client code yourself.
Let’s see it in action. Imagine we have a simple gRPC service defined in proto/helloworld.proto:
syntax = "proto3";
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
To expose this via grpc-gateway, we need to add annotations to our .proto file. These annotations tell grpc-gateway how to map HTTP requests to gRPC methods.
syntax = "proto3";
package helloworld;
import "google/api/annotations.proto"; // Import the annotations
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {
option (google.api.http) = {
get: "/v1/hello/{name}" // Maps GET requests to /v1/hello/{name}
};
}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
Now, we’ll use protoc with the grpc-gateway plugin to generate Go code. You’ll need to install the plugin first: go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@latest.
The command to generate the gateway code looks like this:
protoc --proto_path=proto \
--grpc-gateway_out . \
--grpc-gateway_opt logtostderr=true \
proto/helloworld.proto
This command generates helloworld.pb.gw.go. This file contains the HTTP handler that bridges your gRPC service and the HTTP world.
Next, you need to run your actual gRPC server (which implements the Greeter service) and then start a new HTTP server that uses the generated grpc-gateway handler.
Here’s a simplified Go main function demonstrating how to wire them up:
package main
import (
"context"
"log"
"net/http"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
gw "your_module_path/gen/helloworld" // Generated gateway code
pb "your_module_path/gen/helloworld" // Generated protobuf code
)
func main() {
ctx := context.Background()
// Create a client connection to the gRPC server.
// Replace with your gRPC server address.
grpcEndpoint := "localhost:50051"
conn, err := grpc.DialContext(ctx, grpcEndpoint, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("Failed to dial gRPC server: %v", err)
}
defer conn.Close()
// Create a new gRPC-Gateway mux
mux := runtime.NewServeMux()
// Register the gRPC-Gateway handler for the Greeter service
// The generated code will handle the HTTP to gRPC translation
err = gw.RegisterGreeterHandler(ctx, mux, conn)
if err != nil {
log.Fatalf("Failed to register gRPC-Gateway handler: %v", err)
}
// Start the HTTP server
httpPort := "8080"
log.Printf("HTTP server listening on port %s", httpPort)
http.ListenAndServe(":"+httpPort, mux)
}
In this setup, the main function starts an HTTP server on port 8080. When a request comes in for /v1/hello/World, the grpc-gateway mux, registered with gw.RegisterGreeterHandler, intercepts it. It parses the name from the URL path, constructs a pb.HelloRequest message, and then makes a gRPC call to your actual gRPC server (which must be running on localhost:50051 in this example). The response from the gRPC server is then translated back into an HTTP response and sent to the client.
The core problem grpc-gateway solves is the friction of maintaining two separate API interfaces (gRPC and REST) for the same backend logic. By generating the REST API boilerplate from your gRPC definitions, you ensure consistency and reduce development overhead. The google.api.http annotation is the linchpin, defining the RESTful endpoint, method (GET, POST, etc.), and how URL parameters map to gRPC message fields.
The most surprising thing is how little actual code you write to bridge these two protocols. The magic happens in the generated *.pb.gw.go files, which are produced entirely by protoc and the grpc-gateway plugin. You’re essentially defining your API once in Protobuf, and grpc-gateway handles the translation for both gRPC clients and REST clients.
The mechanism for handling request bodies and query parameters is also quite powerful. For POST or PUT requests, grpc-gateway can map JSON request bodies to your gRPC message fields. Similarly, it can extract fields from query parameters. For example, a POST /v1/users request with a JSON body like {"name": "Alice", "email": "alice@example.com"} could be mapped to a gRPC CreateUser call, with the JSON fields directly populating the CreateUserRequest message.
The next hurdle you’ll likely encounter is handling more complex routing, custom marshaling, and integrating with authentication mechanisms.