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.
- π 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
- π 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
- π οΈ 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
[dependencies]
grpc-graphql-gateway = "0.1"
tokio = { version = "1", features = ["full"] }
tonic = "0.12"use grpc_graphql_gateway::{Gateway, GrpcClient};
const DESCRIPTORS: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/graphql_descriptor.bin"));
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let gateway = Gateway::builder()
.with_descriptor_set_bytes(DESCRIPTORS)
.add_grpc_client(
"greeter.Greeter",
GrpcClient::builder("http://127.0.0.1:50051").connect_lazy()?,
)
.build()?;
gateway.serve("0.0.0.0:8888").await?;
Ok(())
}Your gateway is now running!
- GraphQL HTTP:
http://localhost:8888/graphql - GraphQL WebSocket:
ws://localhost:8888/graphql/ws
Add to your build.rs:
fn main() -> Result<(), Box<dyn std::error::Error>> {
let out_dir = std::env::var("OUT_DIR")?;
tonic_build::configure()
.build_server(false)
.build_client(false)
.file_descriptor_set_path(
std::path::PathBuf::from(&out_dir).join("graphql_descriptor.bin")
)
.compile_protos(&["proto/your_service.proto"], &["proto"])?;
Ok(())
}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
}
}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
}curl http://localhost:8888/graphql \
--form 'operations={"query": "mutation($file: Upload!) { uploadAvatar(input:{userId:\"123\", avatar:$file}) { userId size } }", "variables": {"file": null}}' \
--form 'map={"0": ["variables.file"]}' \
--form '0=@avatar.png'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 }];
}Build federated GraphQL architectures with multiple subgraphs.
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
}];
}The gateway includes production-ready entity resolution with automatic batching:
use grpc_graphql_gateway::{
Gateway, GrpcClient, EntityResolverMapping, GrpcEntityResolver
};
// Configure entity resolver with DataLoader batching
let resolver = GrpcEntityResolver::builder(client_pool)
.register_entity_resolver(
"User",
EntityResolverMapping {
service_name: "UserService".to_string(),
method_name: "GetUser".to_string(),
key_field: "id".to_string(),
}
)
.build();
let gateway = Gateway::builder()
.with_descriptor_set_bytes(DESCRIPTORS)
.enable_federation()
.with_entity_resolver(Arc::new(resolver))
.add_grpc_client("UserService", user_client)
.serve("0.0.0.0:8891")
.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
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"
}];
}| 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" |
# Start your federation subgraphs
cargo run --bin federation
# Compose the supergraph
./examples/federation/compose_supergraph.sh
# Run Apollo Router
router --supergraph examples/federation/supergraph.graphql --devQuery the federated graph:
query {
product(upc: "123") {
upc
name
price
createdBy {
id
name
email # Resolved from User subgraph!
}
}
}use grpc_graphql_gateway::middleware::{Middleware, Context};
struct AuthMiddleware;
#[async_trait::async_trait]
impl Middleware for AuthMiddleware {
async fn call(
&self,
ctx: &mut Context,
next: Box<dyn Fn(&mut Context) -> BoxFuture<'_, Result<()>>>,
) -> Result<()> {
// Validate auth token
let token = ctx.headers().get("authorization")
.ok_or_else(|| Error::Unauthorized)?;
// Add user info to context
ctx.extensions_mut().insert(UserInfo { /* ... */ });
next(ctx).await
}
}
let gateway = Gateway::builder()
.add_middleware(AuthMiddleware)
.build()?;let gateway = Gateway::builder()
.with_error_handler(|error| {
// Log errors, send to monitoring, etc.
tracing::error!("GraphQL Error: {}", error);
error
})
.build()?;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
}
};
}| 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 |
Generate a starter gateway:
# Install the generator
cargo install grpc-graphql-gateway --bin protoc-gen-graphql-template
# Generate gateway code
protoc \
--plugin=protoc-gen-graphql-template=target/debug/protoc-gen-graphql-template \
--graphql-template_out=. \
--proto_path=proto \
proto/federation_example.proto
# Run the generated gateway
cargo run --bin graphqlThe generator creates:
- Complete gateway implementation
- Example queries/mutations/subscriptions
- Service configuration
- Ready-to-run code
Basic query, mutation, subscription, and file upload:
cargo run --bin greeterOpen http://localhost:8888/graphql and try:
query { hello(name: "World") { message } }
mutation { updateGreeting(input: {name: "GraphQL", salutation: "Hey"}) { message } }
subscription { streamHello(name: "Stream") { message } }Complete federated microservices with entity resolution:
cargo run --bin federationDemonstrates:
- 3 federated subgraphs (User, Product, Review)
- Entity resolution with DataLoader batching
- Cross-subgraph queries
@shareablefields- Entity extensions
- 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
- 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
- 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
# Run all tests
cargo test
# Run with logging
RUST_LOG=debug cargo test
# Run specific test
cargo test test_federation_configgrpc-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
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.
This project is licensed under the MIT License - see the LICENSE file for details.
- Inspired by grpc-graphql-gateway (Go)
- Built with async-graphql
- Powered by tonic
- Federation based on Apollo Federation v2
Made with β€οΈ by Protocol Lattice