Skip to content

gRPC Gateway REST — Exposing gRPC Services as RESTful JSON APIs

DodaTech Updated 2026-06-28 5 min read

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

  1. 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.
  2. Ignoring CORS — browsers need CORS headers for cross-origin requests. Add CORS middleware to the gateway HTTP server.
  3. Mapping complex nested messages to query parameters — use body annotations for complex request bodies. Query parameters work best for simple filters and pagination.
  4. Not handling REST-specific errors — gRPC status codes don't map 1:1 to HTTP status codes. Use runtime.HTTPStatusFromCode for proper mapping.
  5. 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

  1. How does grpc-gateway translate REST requests to gRPC calls?
  2. What HTTP annotations are available for mapping REST endpoints?
  3. How do you handle path parameters like /v1/threats/{threat_id}?
  4. Why do you need CORS middleware for browser clients?
  5. 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

Does grpc-gateway support WebSockets?

No. grpc-gateway is for HTTP/1.1 REST only. For real-time features, use gRPC-Web in browsers or direct gRPC in backend services.

Can I use grpc-gateway with authentication?

Yes. Forward HTTP Authorization headers to gRPC metadata. Your gRPC auth interceptor reads the metadata from the gateway's request.

What is the performance overhead of grpc-gateway?

The gateway adds 1-5ms latency per request for JSON marshaling and HTTP-to-gRPC translation. For most use cases, this is negligible.

Can I customize the JSON field names?

Yes. Use the google.api.field_behavior and gogoproto or protoc-gen-go-grpc options to customize JSON field naming and behavior.

How do I generate OpenAPI/Swagger docs?

Use protoc-gen-openapiv2 alongside grpc-gateway. It generates swagger.json from the same protobuf annotations.

What's Next

Learn about gRPC-Web for browser clients

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro