Envoy Gateway + OpenAPI
Full Example
All Envoy Gateway configs described below are available in the production-ready/envoy-gateway directory.
Connectum services communicate via gRPC, but many external clients (browsers, mobile apps, third-party integrations) need REST/JSON APIs. Envoy Gateway provides gRPC-JSON transcoding -- automatically converting REST requests into gRPC calls and vice versa -- without writing any REST handlers.
Request Flow
Prerequisites
- Kubernetes cluster with Envoy Gateway installed
- Proto files with
google.api.httpannotations google/api/annotations.protoandgoogle/api/http.protoare available via buf BSR deps
Step 1: Annotate Proto Files
Add HTTP bindings to your proto service methods using google.api.http. The proto file defines CRUD operations for OrderService with REST path mappings (e.g. POST /v1/orders, GET /v1/orders/{order_id}) and buf validation rules.
See orders.proto for the full proto definition.
TIP
The google/api/http.proto and google/api/annotations.proto files are available through buf BSR deps. Add them to your buf.yaml dependencies and import them directly in your proto files without vendoring.
Step 2: Generate OpenAPI Spec
Generate an OpenAPI v3 specification from your annotated proto files using protoc-gen-openapiv3:
Install
# Install the protoc plugin
go install github.com/google/gnostic/cmd/protoc-gen-openapi@latest
# Or via buf plugin
# Add to buf.gen.yamlbuf.gen.yaml Configuration
This configures buf to generate both ConnectRPC TypeScript stubs (protoc-gen-es) and an OpenAPI v3 spec (protoc-gen-openapi) with proto-style naming and string enums.
See buf.gen.yaml for the full configuration.
Generate
# Generate TypeScript stubs + OpenAPI spec
buf generate
# Output: openapi/mycompany/orders/v1/orders.openapi.yamlExample Generated OpenAPI
openapi: 3.0.3
info:
title: Order Service API
version: 1.0.0
paths:
/v1/orders:
post:
operationId: OrderService_CreateOrder
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/CreateOrderRequest'
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/CreateOrderResponse'
get:
operationId: OrderService_ListOrders
parameters:
- name: page_size
in: query
schema:
type: integer
- name: page_token
in: query
schema:
type: string
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/ListOrdersResponse'
/v1/orders/{order_id}:
get:
operationId: OrderService_GetOrder
parameters:
- name: order_id
in: path
required: true
schema:
type: string
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/Order'Step 3: Generate Proto Descriptor
Envoy's gRPC-JSON transcoder requires a compiled proto descriptor set:
buf build -o proto-descriptor.pb
# Or with protoc directly:
protoc \
--include_imports \
--include_source_info \
--descriptor_set_out=proto-descriptor.pb \
-I proto/ \
proto/mycompany/orders/v1/orders.protoStep 4: Kubernetes Gateway API Resources
GatewayClass and Gateway
This manifest defines a GatewayClass and Gateway with three listeners: HTTP on port 80, HTTPS on port 443 (with TLS termination), and a dedicated gRPC listener on port 9090 for native gRPC clients.
See gateway.yaml for the full manifest.
GRPCRoute (Native gRPC Traffic)
Routes native gRPC traffic directly to the OrderService backend on port 5000, matching on the fully qualified gRPC service name.
See grpc-route.yaml for the full manifest.
HTTPRoute (REST-to-gRPC Transcoding)
Maps REST paths (/v1/orders prefix) to the gRPC backend for transcoding, and routes /docs to the Swagger UI service. Attached to both HTTP and HTTPS listeners.
See http-route.yaml for the full manifest.
Step 5: Envoy Filter for gRPC-JSON Transcoding
If your Envoy Gateway version supports EnvoyPatchPolicy, configure the transcoder filter directly. This patch injects the grpc_json_transcoder HTTP filter into the listener chain, configuring it with the proto descriptor, target services, and JSON print options (whitespace, primitive fields, proto field names).
See envoy-patch-policy.yaml for the full manifest.
Alternatively, store the proto descriptor in a ConfigMap. This creates a ConfigMap with the base64-encoded proto descriptor binary that can be mounted into Envoy pods.
See proto-descriptor-configmap.yaml for the full manifest.
Step 6: Swagger UI Deployment
Deploy Swagger UI to serve the generated OpenAPI spec. This manifest includes a ConfigMap for the OpenAPI spec, a Deployment running the official swaggerapi/swagger-ui image with the spec mounted at /specs, and a ClusterIP Service exposing port 8080.
See swagger-ui.yaml for the full manifest.
Rate Limiting
Add rate limiting at the gateway level to protect your Connectum services. This BackendTrafficPolicy applies local rate limiting of 100 requests per second to the REST HTTPRoute.
See rate-limit.yaml for the full manifest.
Load Balancing
Configure request-level (L7) load balancing for gRPC backends. This is essential because gRPC uses persistent HTTP/2 connections. The policy applies RoundRobin load balancing to the REST HTTPRoute.
See backend-traffic-policy.yaml for the full manifest.
Full Example: End-to-End Request
REST Client Sends Request
# Create an order via REST
curl -X POST https://api.example.com/v1/orders \
-H "Content-Type: application/json" \
-d '{
"customer_id": "cust-123",
"items": [
{"sku": "ITEM-001", "quantity": 2}
]
}'
# Get an order via REST
curl https://api.example.com/v1/orders/550e8400-e29b-41d4-a716-446655440000What Happens Under the Hood
- REST request arrives at Envoy Gateway on port 80/443
- HTTPRoute matches
/v1/ordersprefix - Envoy's
grpc_json_transcoderfilter converts JSON to gRPC binary using the proto descriptor - Request is forwarded to
order-service:5000as a native gRPC call - Connectum service processes the gRPC request through its interceptor chain
- gRPC response is converted back to JSON by the transcoder
- JSON response is returned to the client
Native gRPC Client (Unchanged)
import { createClient } from '@connectrpc/connect';
import { createGrpcTransport } from '@connectrpc/connect-node';
import { OrderService } from '#gen/mycompany/orders/v1/orders_pb.js';
const transport = createGrpcTransport({
baseUrl: 'http://api.example.com:9090',
httpVersion: '2',
});
const client = createClient(OrderService, transport);
const order = await client.getOrder({ orderId: '550e8400-e29b-41d4-a716-446655440000' });CI/CD: Automate Proto Descriptor Generation
Add proto descriptor generation to your CI pipeline so Envoy always has an up-to-date descriptor:
# .github/workflows/proto.yml (excerpt)
- name: Generate proto descriptor
run: buf build -o proto-descriptor.pb
- name: Update ConfigMap
run: |
kubectl create configmap proto-descriptors \
--from-file=proto-descriptor.pb \
--namespace=connectum \
--dry-run=client -o yaml | kubectl apply -f -
- name: Restart Gateway (pick up new descriptor)
run: kubectl rollout restart deployment envoy-gateway -n connectumWARNING
When you add new services or change HTTP annotations, you must regenerate the proto descriptor and update the Envoy configuration. Automate this in CI to prevent drift between proto definitions and the gateway configuration.
What's Next
- Service Mesh with Istio -- Automatic mTLS and traffic splitting
- Kubernetes Deployment -- Core deployment manifests
- Architecture Patterns -- Service communication patterns
