Implementing gRPC Gateway

1. Understanding gRPC Gateway

ConceptDetail
PurposeGenerate REST/JSON proxy from .proto with HTTP annotations
RuntimeHTTP server in same process, forwards to gRPC server
AlternativeEnvoy gRPC-JSON transcoder, Connect-RPC

2. Generating Gateway Code

Example: buf.gen.yaml entry

plugins:
  - remote: buf.build/grpc-ecosystem/gateway
    out: gen
    opt: paths=source_relative
  - remote: buf.build/grpc-ecosystem/openapiv2
    out: gen/openapi

3. Creating Gateway Server

Example: Mux + register

mux := runtime.NewServeMux()
_ = userv1.RegisterUserServiceHandlerFromEndpoint(ctx, mux,
    "localhost:50051", []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())})
http.ListenAndServe(":8080", mux)

4. Registering Gateway Handlers

APIDetail
RegisterXServiceHandlerFrom existing conn
RegisterXServiceHandlerFromEndpointDial endpoint
RegisterXServiceHandlerServerIn-process — no network hop

5. Adding HTTP Annotations

Example: HTTP rules

service UserService {
  rpc GetUser(GetUserRequest) returns (User) {
    option (google.api.http) = { get: "/v1/users/{id}" };
  }
  rpc CreateUser(CreateUserRequest) returns (User) {
    option (google.api.http) = { post: "/v1/users" body: "user" };
  }
  rpc UpdateUser(UpdateUserRequest) returns (User) {
    option (google.api.http) = { patch: "/v1/users/{user.id}" body: "user" };
  }
}

6. Configuring REST Paths

PatternEffect
/v1/users/{id}Path var maps to request.id
{name=projects/*/users/*}Resource-name pattern
additional_bindingsMultiple URL mappings

7. Mapping HTTP Methods

RPC VerbConvention
Get / ListGET
CreatePOST
UpdatePUT or PATCH
DeleteDELETE
Custom actionPOST with verb in path

8. Handling Query Parameters

RuleDetail
Unmapped scalar fieldsBecome query params automatically
Repeated?tags=a&tags=b
Nested?user.id=1

9. Handling Path Parameters

PatternDetail
{id}Maps to scalar field
{path=**}Captures remaining path
ValidationType-checked from proto

10. Configuring Custom Marshaling

Example: Custom JSON marshaler

mux := runtime.NewServeMux(
    runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.JSONPb{
        MarshalOptions: protojson.MarshalOptions{EmitUnpopulated: true, UseProtoNames: false},
        UnmarshalOptions: protojson.UnmarshalOptions{DiscardUnknown: true},
    }),
)
OptionEffect
EmitUnpopulatedInclude default-valued fields
UseProtoNamessnake_case vs camelCase
DiscardUnknownIgnore unknown JSON fields