Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
grpc-graphql-gateway
A high-performance Rust gateway that bridges gRPC services to GraphQL with full Apollo Federation v2 support.
Transform your gRPC microservices into a unified GraphQL API with zero GraphQL code. This gateway dynamically generates GraphQL schemas from protobuf descriptors and routes requests to your gRPC backends via Tonic, providing a seamless bridge between gRPC and GraphQL ecosystems.
โจ Features
Core Capabilities
- ๐ Dynamic Schema Generation - Automatic GraphQL schema from protobuf descriptors
- โก Full Operation Support - Queries, Mutations, and Subscriptions
- ๐ WebSocket Subscriptions - Real-time data via GraphQL subscriptions (
graphql-wsprotocol) - ๐ค File Uploads - Multipart form data support for file uploads
- ๐ฏ Type Safety - Leverages Rust's type system for robust schema generation
Federation & Enterprise
- ๐ Apollo Federation v2 - Complete federation support with entity resolution
- ๐ Entity Resolution - Production-ready resolver with DataLoader batching
- ๐ซ No N+1 Queries - Built-in DataLoader prevents performance issues
- ๐ All Federation Directives -
@key,@external,@requires,@provides,@shareable - ๐ Batch Operations - Efficient entity resolution with automatic batching
Developer Experience
- ๐ ๏ธ Code Generation -
protoc-gen-graphql-templategenerates starter gateway code - ๐ง Middleware Support - Extensible middleware for auth, logging, and observability
- ๐ Rich Examples - Complete working examples for all features
- ๐งช Well Tested - Comprehensive test coverage
Production Ready
- ๐ฅ Health Checks -
/healthand/readyendpoints for Kubernetes liveness/readiness probes - ๐ Prometheus Metrics -
/metricsendpoint with request counts, latencies, and error rates - ๐ญ OpenTelemetry Tracing - Distributed tracing with GraphQL and gRPC span tracking
- ๐ก๏ธ DoS Protection - Query depth and complexity limiting to prevent expensive queries
- ๐ Introspection Control - Disable schema introspection in production for security
- โก Rate Limiting - Built-in rate limiting middleware
- ๐ฆ Automatic Persisted Queries (APQ) - Reduce bandwidth with query hash caching
- ๐ Circuit Breaker - Prevent cascading failures with automatic backend health management
- ๏ฟฝ Response Caching - In-memory LRU cache with TTL and mutation-triggered invalidation
- ๏ฟฝ๐ Batch Queries - Execute multiple GraphQL operations in a single HTTP request
- ๐ Graceful Shutdown - Clean server shutdown with in-flight request draining
๐ Quick Start
Installation
[]
= "0.2"
= { = "1", = ["full"] }
= "0.12"
Basic Gateway
use ;
const DESCRIPTORS: & = include_bytes!;
async
Your gateway is now running!
- GraphQL HTTP:
http://localhost:8888/graphql - GraphQL WebSocket:
ws://localhost:8888/graphql/ws
Generate Descriptors
Add to your build.rs:
๐ Usage Examples
Queries, Mutations & Subscriptions
Annotate your proto file with GraphQL directives:
service UserService {
option (graphql.service) = {
host: "localhost:50051"
insecure: true
};
// Query
rpc GetUser(GetUserRequest) returns (User) {
option (graphql.schema) = {
type: QUERY
name: "user"
};
}
// Mutation
rpc CreateUser(CreateUserRequest) returns (User) {
option (graphql.schema) = {
type: MUTATION
name: "createUser"
request { name: "input" }
};
}
// Subscription (server streaming)
rpc WatchUser(WatchUserRequest) returns (stream User) {
option (graphql.schema) = {
type: SUBSCRIPTION
name: "userUpdates"
};
}
}
GraphQL operations:
# Query
query {
user(id: "123") {
id
name
email
}
}
# Mutation
mutation {
createUser(input: { name: "Alice", email: "alice@example.com" }) {
id
name
}
}
# Subscription
subscription {
userUpdates(id: "123") {
id
name
status
}
}
File Uploads
The gateway automatically supports GraphQL file uploads via multipart requests:
message UploadAvatarRequest {
string user_id = 1;
bytes avatar = 2; // Maps to Upload scalar in GraphQL
}
Field-Level Control
message User {
string id = 1 [(graphql.field) = { required: true }];
string email = 2 [(graphql.field) = { name: "emailAddress" }];
string internal_id = 3 [(graphql.field) = { omit: true }];
string password_hash = 4 [(graphql.field) = { omit: true }];
}
๐ Apollo Federation v2
Build federated GraphQL architectures with multiple subgraphs.
Defining Entities
message User {
option (graphql.entity) = {
keys: "id"
resolvable: true
};
string id = 1 [(graphql.field) = { required: true }];
string email = 2 [(graphql.field) = { shareable: true }];
string name = 3 [(graphql.field) = { shareable: true }];
}
message Product {
option (graphql.entity) = {
keys: "upc"
resolvable: true
};
string upc = 1 [(graphql.field) = { required: true }];
string name = 2 [(graphql.field) = { shareable: true }];
int32 price = 3 [(graphql.field) = { shareable: true }];
User created_by = 4 [(graphql.field) = {
name: "createdBy"
shareable: true
}];
}
Entity Resolution with DataLoader
The gateway includes production-ready entity resolution with automatic batching:
use ;
// Configure entity resolver with DataLoader batching
let resolver = builder
.register_entity_resolver
.build;
let gateway = builder
.with_descriptor_set_bytes
.enable_federation
.with_entity_resolver
.add_grpc_client
.serve
.await?;
Benefits:
- โ No N+1 Queries - DataLoader batches concurrent entity requests
- โ Automatic Batching - Multiple entities resolved in single operation
- โ Production Ready - Comprehensive error handling and logging
Extending Entities
message UserReviews {
option (graphql.entity) = {
extend: true
keys: "id"
};
string id = 1 [(graphql.field) = {
external: true
required: true
}];
repeated Review reviews = 2 [(graphql.field) = {
requires: "id"
}];
}
Federation Directives
| Directive | Purpose | Example |
|---|---|---|
@key |
Define entity key fields | keys: "id" |
@shareable |
Field resolvable from multiple subgraphs | shareable: true |
@external |
Field defined in another subgraph | external: true |
@requires |
Fields needed from other subgraphs | requires: "id email" |
@provides |
Fields this resolver provides | provides: "id name" |
Running with Apollo Router
# Start your federation subgraphs
# Compose the supergraph
# Run Apollo Router
Query the federated graph:
query {
product(upc: "123") {
upc
name
price
createdBy {
id
name
email # Resolved from User subgraph!
}
}
}
๐ง Advanced Features
Middleware
use ;
;
let gateway = builder
.add_middleware
.build?;
DoS Protection (Query Limits)
Protect your gateway and gRPC backends from malicious or expensive queries:
let gateway = builder
.with_descriptor_set_bytes
.with_query_depth_limit // Max nesting depth
.with_query_complexity_limit // Max query cost
.add_grpc_client
.build?;
Query Depth Limiting prevents deeply nested queries that could overwhelm your backends:
# This would be blocked if depth exceeds limit
query {
users { # depth 1
friends { # depth 2
friends { # depth 3
friends { # depth 4 - blocked if limit < 4
name
}
}
}
}
}
Query Complexity Limiting caps the total "cost" of a query (each field = 1 by default):
# Complexity = 4 (users + friends + name + email)
query {
users {
friends {
name
email
}
}
}
Recommended Values:
| Use Case | Depth Limit | Complexity Limit |
|---|---|---|
| Public API | 5-10 | 50-100 |
| Authenticated Users | 10-15 | 100-500 |
| Internal/Trusted | 15-25 | 500-1000 |
Health Checks
Enable Kubernetes-compatible health check endpoints:
let gateway = builder
.with_descriptor_set_bytes
.enable_health_checks // Adds /health and /ready endpoints
.add_grpc_client
.build?;
Endpoints:
| Endpoint | Purpose | Response |
|---|---|---|
GET /health |
Liveness probe | 200 OK if server is running |
GET /ready |
Readiness probe | 200 OK if gRPC clients are configured |
Kubernetes Deployment:
livenessProbe:
httpGet:
path: /health
port: 8888
initialDelaySeconds: 5
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8888
initialDelaySeconds: 5
periodSeconds: 10
Prometheus Metrics
Enable Prometheus-compatible metrics endpoint:
let gateway = builder
.with_descriptor_set_bytes
.enable_metrics // Adds /metrics endpoint
.add_grpc_client
.build?;
Metrics Exposed:
| Metric | Type | Description |
|---|---|---|
graphql_requests_total |
Counter | Total requests by operation type |
graphql_request_duration_seconds |
Histogram | Request latency |
graphql_errors_total |
Counter | Errors by type |
grpc_backend_requests_total |
Counter | gRPC backend calls |
grpc_backend_duration_seconds |
Histogram | gRPC latency |
Prometheus Scrape Config:
scrape_configs:
- job_name: 'graphql-gateway'
static_configs:
- targets:
metrics_path: '/metrics'
OpenTelemetry Tracing
Enable distributed tracing for end-to-end visibility:
use ;
// Initialize the tracer (do this once at startup)
let config = new
.with_service_name
.with_sample_ratio; // Sample all requests
let _provider = init_tracer;
let gateway = builder
.with_descriptor_set_bytes
.enable_tracing
.add_grpc_client
.build?;
// ... run your server ...
// Shutdown on exit
shutdown_tracer;
Spans Created:
| Span | Kind | Attributes |
|---|---|---|
graphql.query |
Server | graphql.operation.name, graphql.document |
graphql.mutation |
Server | graphql.operation.name, graphql.document |
grpc.call |
Client | rpc.service, rpc.method, rpc.grpc.status_code |
OTLP Export (Optional):
[]
= { = "0.1", = ["otlp"] }
Schema Introspection Control
Disable introspection in production for security:
let gateway = builder
.with_descriptor_set_bytes
.disable_introspection // Block __schema and __type queries
.add_grpc_client
.build?;
Environment-Based Toggle:
let is_production = var.map.unwrap_or;
let mut builder = builder
.with_descriptor_set_bytes;
if is_production
let gateway = builder.build?;
Automatic Persisted Queries (APQ)
Reduce bandwidth by caching queries on the server and allowing clients to send query hashes:
use ;
use Duration;
let gateway = builder
.with_descriptor_set_bytes
.with_persisted_queries
.add_grpc_client
.build?;
How APQ Works:
- First request: Client sends hash only โ Gateway returns
PERSISTED_QUERY_NOT_FOUND - Retry: Client sends hash + full query โ Gateway caches and executes
- Subsequent requests: Client sends hash only โ Gateway uses cached query
Client Request Format:
Benefits:
- โ Reduces request payload size by ~90% for large queries
- โ Compatible with Apollo Client's APQ implementation
- โ LRU eviction prevents unbounded memory growth
- โ Optional TTL for cache expiration
Circuit Breaker
Protect your gateway from cascading failures when backend services are unhealthy:
use ;
use Duration;
let gateway = builder
.with_descriptor_set_bytes
.with_circuit_breaker
.add_grpc_client
.build?;
How It Works:
- Closed: Normal operation, all requests flow through
- Open: After
failure_thresholdconsecutive failures, circuit opens โ requests fail fast - Half-Open: After
recovery_timeout, limited test requests are allowed - Recovery: If test requests succeed, circuit closes; if they fail, it reopens
Benefits:
- โ Prevents cascading failures when backends are unhealthy
- โ Fast-fail reduces latency (no waiting for timeouts)
- โ Automatic recovery testing when services come back
- โ Per-service circuit breakers (one failing service doesn't affect others)
Circuit States:
| State | Description |
|---|---|
Closed |
Normal operation |
Open |
Failing fast, returning SERVICE_UNAVAILABLE |
HalfOpen |
Testing if service recovered |
Response Caching
Dramatically improve performance with in-memory response caching:
use ;
use Duration;
let gateway = builder
.with_descriptor_set_bytes
.with_response_cache
.add_grpc_client
.build?;
How It Works:
- First Query: Cache miss โ Execute gRPC โ Cache response โ Return
- Second Query: Cache hit โ Return cached response immediately (<1ms)
- Mutation: Execute mutation โ Invalidate related cache entries
- Next Query: Cache miss (invalidated) โ Execute gRPC โ Cache โ Return
Example with curl (greeter service):
# Start the gateway
# 1. First query - cache miss, hits gRPC backend
# Response: {"data":{"sayHello":{"message":"Hello Alice!"}}}
# Logs: "Response cache miss" or no cache log
# 2. Same query - cache hit, instant response
# Response: {"data":{"sayHello":{"message":"Hello Alice!"}}}
# Logs: "Response cache hit"
# 3. Mutation - invalidates cache
# Response: {"data":{"updateGreeting":{"message":"Hi Alice!"}}}
# Logs: "Cache invalidated after mutation"
# 4. Query again - cache miss (was invalidated by mutation)
# Response: {"data":{"sayHello":{"message":"Hi Alice!"}}} <- Fresh data!
# Logs: "Response cache miss"
What Gets Cached:
| Operation | Cached? | Triggers Invalidation? |
|---|---|---|
| Query | โ Yes | No |
| Mutation | โ No | โ Yes |
| Subscription | โ No | No |
Cache Invalidation Strategies:
- TTL-Based: Entries expire after
default_ttl - Mutation-Based: Mutations automatically invalidate related cache entries
- Type-Based: Invalidate by GraphQL type (e.g., all
Userqueries) - Entity-Based: Invalidate by entity ID (e.g.,
User#123)
Benefits:
- โ Sub-millisecond response times for cached queries
- โ Reduced gRPC backend load (10-100x fewer calls)
- โ Automatic cache invalidation on mutations
- โ Stale-while-revalidate for best UX
- โ Zero external dependencies (pure in-memory)
Graceful Shutdown
Enable production-ready server lifecycle management with graceful shutdown:
use ;
use Duration;
let gateway = builder
.with_descriptor_set_bytes
.with_graceful_shutdown
.add_grpc_client
.serve
.await?;
How It Works:
- Signal Received: SIGTERM, SIGINT, or Ctrl+C is received
- Stop Accepting: Server stops accepting new connections
- Drain Requests: In-flight requests are allowed to complete (up to timeout)
- Cleanup: Active subscriptions are cancelled, resources are released
- Exit: Server shuts down gracefully
Custom Shutdown Signal:
use oneshot;
let = ;
// Trigger shutdown after some condition
spawn;
builder
.with_descriptor_set_bytes
.add_grpc_client
.serve_with_shutdown
.await?;
Benefits:
- โ Clean shutdown with no dropped requests
- โ Automatic OS signal handling (SIGTERM, SIGINT, Ctrl+C)
- โ Configurable timeout for in-flight request draining
- โ Active subscription cleanup
- โ Kubernetes-compatible (responds to SIGTERM)
Batch Queries
Execute multiple GraphQL operations in a single HTTP request for improved performance:
Single Query (standard):
Batch Queries (multiple operations):
Batch Response:
Benefits:
- โ Reduces HTTP overhead for multiple operations
- โ Automatic support - no configuration required
- โ Compatible with Apollo Client batch link
- โ Each query in batch supports APQ and middleware
- โ Backward compatible - single queries work unchanged
Use Cases:
- Fetching data for multiple UI components in one request
- Executing related mutations together
- Reducing latency on mobile/slow networks
Custom Error Handling
let gateway = builder
.with_error_handler
.build?;
Response Plucking
Extract nested fields as top-level responses:
message ListUsersResponse {
repeated User users = 1;
int32 total = 2;
}
rpc ListUsers(ListUsersRequest) returns (ListUsersResponse) {
option (graphql.schema) = {
type: QUERY
name: "users"
response {
pluck: "users" // Returns [User] instead of ListUsersResponse
}
};
}
๐ Type Mapping
| Protobuf | GraphQL |
|---|---|
string |
String |
bool |
Boolean |
int32, uint32 |
Int |
int64, uint64 |
String (avoids precision loss) |
float, double |
Float |
bytes |
Upload (input) / String (output, base64) |
repeated T |
[T] |
message |
Object / InputObject |
enum |
Enum |
๐ ๏ธ Code Generation
Generate a starter gateway:
# Install the generator
# Generate gateway code
# Run the generated gateway
The generator creates:
- Complete gateway implementation
- Example queries/mutations/subscriptions
- Service configuration
- Ready-to-run code
๐ Examples
Greeter Example
Basic query, mutation, subscription, and file upload:
Open http://localhost:8888/graphql and try:
query { hello(name: "World") { message } }
mutation { updateGreeting(input: {name: "GraphQL", salutation: "Hey"}) { message } }
subscription { streamHello(name: "Stream") { message } }
Federation Example
Complete federated microservices with entity resolution:
Demonstrates:
- 3 federated subgraphs (User, Product, Review)
- Entity resolution with DataLoader batching
- Cross-subgraph queries
@shareablefields- Entity extensions
๐ฏ Best Practices
Federation
- Define Clear Boundaries - Each subgraph owns its entities
- Use @shareable Wisely - Mark fields resolved by multiple subgraphs
- Leverage DataLoader - Prevent N+1 queries with batch resolution
- Composite Keys - Use when entities need multiple identifiers
- Minimize @requires - Only specify truly required fields
Performance
- Enable Connection Pooling - Reuse gRPC connections
- Use Lazy Connections - Connect on first use
- Implement Caching - Cache frequently accessed entities
- Batch Operations - Use DataLoader for entity resolution
- Monitor Metrics - Track query performance and batch sizes
Security
- Validate Inputs - Use field-level validation
- Omit Sensitive Fields - Use
omit: truefor internal data - Implement Auth Middleware - Centralize authentication
- Rate Limiting - Protect against abuse
- TLS/SSL - Secure gRPC connections in production
๐งช Testing
# Run all tests
# Run with logging
RUST_LOG=debug
# Run specific test
๐ฆ Project Structure
grpc-graphql-gateway-rs/
โโโ src/
โ โโโ lib.rs # Public API
โ โโโ gateway.rs # Gateway implementation
โ โโโ schema.rs # Schema builder
โ โโโ federation.rs # Federation support
โ โโโ dataloader.rs # DataLoader for batching
โ โโโ grpc_client.rs # gRPC client management
โ โโโ middleware.rs # Middleware system
โ โโโ runtime.rs # HTTP/WebSocket server
โโโ proto/
โ โโโ graphql.proto # GraphQL annotations
โ โโโ *.proto # Your service definitions
โโโ examples/
โ โโโ greeter/ # Basic example
โ โโโ federation/ # Federation example
โโโ tests/ # Integration tests
๐ค Contributing
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
๐ License
This project is licensed under the MIT License - see the LICENSE file for details.
๐ Acknowledgments
- Inspired by grpc-graphql-gateway (Go)
- Built with async-graphql
- Powered by tonic
- Federation based on Apollo Federation v2
๐ Links
Made with โค๏ธ by Protocol Lattice