gRPC Gateway REST — Exposing gRPC Services as RESTful JSON APIs
In this tutorial, you will learn about grpc gateway rest. We cover key concepts, practical examples, and best practices to help you master this topic.
gRPC Gateway generates RESTful JSON APIs from protobuf service definitions using HTTP annotations, enabling gRPC services to be consumed by browsers, mobile apps, and legacy systems that don't support HTTP/2 or protobuf.
What You'll Learn
- Setting up grpc-gateway
- HTTP annotations in protobuf files
- Generating REST endpoints with protoc
- Query parameter and path parameter mapping
- Error handling and response formatting
- CORS and browser support
Why It Matters
Not all clients can speak gRPC. Browsers, mobile apps, and third-party integrations often require REST/JSON APIs. gRPC Gateway generates these automatically from your protobuf definitions, keeping one source of truth for both protocols. DodaTech's Durga Antivirus Pro uses grpc-gateway to expose its internal gRPC Microservices as RESTful APIs for the web dashboard and third-party integrations.
Real-World Use
A third-party SIEM system integrates with Durga Antivirus Pro via REST/JSON. The internal threat analysis service is gRPC-only. grpc-gateway translates the SIEM's REST requests to gRPC calls and converts protobuf responses to JSON, all without writing a single line of translation code.
flowchart LR
A["Browser/REST Client"] --> B["grpc-gateway"]
B --> C["gRPC Server"]
B --> D["Swagger UI"]
A -->|GET /v1/threats| B
B -->|ListThreats(request)| C
C -->|ThreatListResponse| B
B -->|JSON Response| A
style B fill:#fef3c7,stroke:#d97706
style C fill:#dbeafe,stroke:#2563eb
Code Examples
Example 1: Protobuf with HTTP Annotations
syntax = "proto3";
package threat.v1;
import "google/api/annotations.proto";
import "google/api/field_behavior.proto";
import "protoc-gen-openapiv2/options/annotations.proto";
service ThreatService {
rpc ListThreats(ListThreatsRequest) returns (ListThreatsResponse) {
option (google.api.http) = {
get: "/v1/threats"
};
}
rpc GetThreat(GetThreatRequest) returns (Threat) {
option (google.api.http) = {
get: "/v1/threats/{threat_id}"
};
}
rpc ReportThreat(ReportThreatRequest) returns (Threat) {
option (google.api.http) = {
post: "/v1/threats"
body: "threat"
};
}
rpc UpdateThreat(UpdateThreatRequest) returns (Threat) {
option (google.api.http) = {
put: "/v1/threats/{threat.threat_id}"
body: "threat"
};
}
rpc DeleteThreat(DeleteThreatRequest) returns (DeleteThreatResponse) {
option (google.api.http) = {
delete: "/v1/threats/{threat_id}"
};
}
}
message ListThreatsRequest {
int32 page_size = 1 [(google.api.field_behavior) = OPTIONAL];
string page_token = 2 [(google.api.field_behavior) = OPTIONAL];
string severity = 3 [(google.api.field_behavior) = OPTIONAL];
}
message Threat {
string threat_id = 1;
string name = 2;
string severity = 3;
string device_id = 4;
string status = 5;
}
Example 2: Generating and Running the Gateway
# Install tools
go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@latest
go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2@latest
# Generate gateway code
protoc -I . \
--grpc-gateway_out . \
--grpc-gateway_opt generate_unbound_methods=true \
--openapiv2_out . \
threat/v1/threat.proto
package main
import (
"context"
"net/http"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
threatpb "path/to/threat/v1"
)
func main() {
ctx := context.Background()
mux := runtime.NewServeMux()
opts := []grpc.DialOption{
grpc.WithTransportCredentials(insecure.NewCredentials()),
}
// Register gRPC server endpoint
err := threatpb.RegisterThreatServiceHandlerFromEndpoint(
ctx, mux, "localhost:50051", opts,
)
if err != nil {
panic(err)
}
// Serve REST API
http.ListenAndServe(":8080", mux)
}
Example 3: Custom Error Handling and Middleware
package main
import (
"net/http"
"strings"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"google.golang.org/grpc/status"
)
// Custom error handler
func customErrorHandler(ctx context.Context,
mux *runtime.ServeMux,
marshaler runtime.Marshaler,
w http.ResponseWriter,
req *http.Request,
err error) {
st := status.Convert(err)
// Map gRPC codes to HTTP status codes
httpStatus := runtime.HTTPStatusFromCode(st.Code())
// Add CORS headers for browser clients
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(httpStatus)
// Format error response
errorResponse := map[string]interface{}{
"code": httpStatus,
"message": st.Message(),
"details": st.Details(),
}
runtime.DefaultHTTPError(ctx, mux, marshaler, w, req, err)
}
// CORS middleware
func corsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods",
"GET, POST, PUT, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers",
"Content-Type, Authorization")
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
next.ServeHTTP(w, r)
})
}
func main() {
mux := runtime.NewServeMux(
runtime.WithErrorHandler(customErrorHandler),
)
// Wrap with CORS
handler := corsMiddleware(mux)
http.ListenAndServe(":8080", handler)
}
Common Mistakes
- Not generating the gateway code — adding HTTP annotations to protobuf is only half the work. You must run protoc with the grpc-gateway plugin to generate the gateway server code.
- Ignoring CORS — browsers need CORS headers for cross-origin requests. Add CORS middleware to the gateway HTTP server.
- Mapping complex nested messages to query parameters — use body annotations for complex request bodies. Query parameters work best for simple filters and pagination.
- Not handling REST-specific errors — gRPC status codes don't map 1:1 to HTTP status codes. Use runtime.HTTPStatusFromCode for proper mapping.
- Running gateway and gRPC server on the same port — the gateway is an HTTP server that proxies to the gRPC server. They need different ports or paths.
Practice Questions
- How does grpc-gateway translate REST requests to gRPC calls?
- What HTTP annotations are available for mapping REST endpoints?
- How do you handle path parameters like /v1/threats/{threat_id}?
- Why do you need CORS middleware for browser clients?
- How are gRPC errors mapped to HTTP status codes?
Challenge: Design a RESTful API using grpc-gateway for a threat management system with: CRUD operations, filtering, pagination, nested resource routes (devices/{id}/threats), and proper HTTP status code mapping for all error conditions.
Mini Project
Build a complete REST-to-gRPC gateway for a threat analysis service with: 10+ REST endpoints generated from protobuf, proper CORS configuration, custom error handling with HTTP status codes, Swagger/OpenAPI documentation generation, and authentication token forwarding from HTTP headers to gRPC metadata.
FAQ
What's Next
Learn about gRPC-Web for browser clients
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro