Docs/Advanced Features

REST API Connectors

The gateway supports REST API Connectors, enabling hybrid architectures where GraphQL fields can resolve data from both gRPC services and REST APIs. This is perfect for gradual migrations, integrating third-party APIs, or bridging legacy systems.

Quick Start

rust
use grpc_graphql_gateway::{Gateway, RestConnector, RestEndpoint, HttpMethod};
use std::time::Duration;

let rest_connector = RestConnector::builder()
    .base_url("https://api.example.com")
    .timeout(Duration::from_secs(30))
    .default_header("Accept", "application/json")
    .add_endpoint(RestEndpoint::new("getUser", "/users/{id}")
        .method(HttpMethod::GET)
        .response_path("$.data")
        .description("Fetch a user by ID"))
    .add_endpoint(RestEndpoint::new("createUser", "/users")
        .method(HttpMethod::POST)
        .body_template(r#"{"name": "{name}", "email": "{email}"}"#))
    .build()?;

let gateway = Gateway::builder()
    .with_descriptor_set_bytes(DESCRIPTORS)
    .add_rest_connector("users_api", rest_connector)
    .add_grpc_client("UserService", grpc_client)
    .build()?;

GraphQL Schema Integration

REST endpoints are automatically exposed as GraphQL fields. The gateway generates:

Field names use the endpoint name directly (e.g., getUser, createPost).

Example GraphQL Queries

graphql
# Query a REST endpoint (GET /users/{id})
query {
  getUser(id: "123")
}

# Mutation to create via REST (POST /users)
mutation {
  createUser(name: "Alice", email: "alice@example.com")
}

Example Response

json
{
  "data": {
    "getUser": {
      "id": 123,
      "name": "Alice",
      "email": "alice@example.com"
    }
  }
}

REST responses are returned as the JSON scalar type, preserving the full structure from the API.

RestConnector

The RestConnector is the main entry point for REST API integration.

Builder Methods

Method Description
base_url(url) Required. Base URL for all endpoints
timeout(duration) Default timeout (default: 30s)
default_header(key, value) Add header to all requests
retry(config) Custom retry configuration
no_retry() Disable retries
log_bodies(true) Enable request/response body logging
with_cache(size) Enable LRU response cache for GET requests
interceptor(interceptor) Add request interceptor
transformer(transformer) Custom response transformer
add_endpoint(endpoint) Add a REST endpoint

RestEndpoint

Define individual REST endpoints with flexible configuration.

rust
use grpc_graphql_gateway::{RestEndpoint, HttpMethod};

let endpoint = RestEndpoint::new("getUser", "/users/{id}")
    .method(HttpMethod::GET)
    .header("X-Custom-Header", "value")
    .query_param("include", "profile")
    .response_path("$.data.user")
    .timeout(Duration::from_secs(10))
    .description("Fetch a user by ID")
    .return_type("User");

Path Templates

Use {variable} placeholders in paths:

rust
RestEndpoint::new("getOrder", "/users/{userId}/orders/{orderId}")

When called with { "userId": "123", "orderId": "456" }, resolves to:

Code
/users/123/orders/456

Query Parameters

Add templated query parameters:

rust
RestEndpoint::new("searchUsers", "/users")
    .query_param("q", "{query}")
    .query_param("limit", "{limit}")

Body Templates

For POST/PUT/PATCH, define request body templates:

rust
RestEndpoint::new("createUser", "/users")
    .method(HttpMethod::POST)
    .body_template(r#"{
        "name": "{name}",
        "email": "{email}",
        "role": "{role}"
    }"#)

If no body template is provided, arguments are automatically serialized as JSON.

Response Extraction

Extract nested data from responses using JSONPath:

rust
// API returns: { "status": "ok", "data": { "user": { "id": "123" } } }
RestEndpoint::new("getUser", "/users/{id}")
    .response_path("$.data.user")  // Returns just the user object

Supported JSONPath:

Typed Responses

By default, REST endpoints return a JSON scalar blob. To enable field selection in GraphQL queries (e.g. { getUser { name email } }), you can define a response schema:

rust
use grpc_graphql_gateway::{RestResponseSchema, RestResponseField};

RestEndpoint::new("getUser", "/users/{id}")
    .with_response_schema(RestResponseSchema::new("User")
        .field(RestResponseField::int("id"))
        .field(RestResponseField::string("name"))
        .field(RestResponseField::string("email"))
        // Define a nested object field
        .field(RestResponseField::object("address", "Address"))
    )

This registers a User type in the schema and allows clients to select only the fields they need.

Mutations vs Queries

Endpoints are automatically classified:

Override explicitly:

rust
// Force a POST to be a query (e.g., search endpoint)
RestEndpoint::new("searchUsers", "/users/search")
    .method(HttpMethod::POST)
    .as_query()

HTTP Methods

rust
use grpc_graphql_gateway::HttpMethod;

HttpMethod::GET      // Read operations
HttpMethod::POST     // Create operations
HttpMethod::PUT      // Full update
HttpMethod::PATCH    // Partial update
HttpMethod::DELETE   // Delete operations

Authentication

Bearer Token

rust
use grpc_graphql_gateway::{RestConnector, BearerAuthInterceptor};
use std::sync::Arc;

let connector = RestConnector::builder()
    .base_url("https://api.example.com")
    .interceptor(Arc::new(BearerAuthInterceptor::new("your-token")))
    .build()?;

The interceptor adds: Authorization: Bearer your-token

API Key

rust
use grpc_graphql_gateway::{RestConnector, ApiKeyInterceptor};
use std::sync::Arc;

let connector = RestConnector::builder()
    .base_url("https://api.example.com")
    .interceptor(Arc::new(ApiKeyInterceptor::x_api_key("your-api-key")))
    .build()?;

The interceptor adds: X-API-Key: your-api-key

Custom Interceptor

Implement the RequestInterceptor trait for custom auth:

rust
use grpc_graphql_gateway::{RequestInterceptor, RestRequest, Result};
use async_trait::async_trait;

struct CustomAuthInterceptor {
    // Your auth logic
}

#[async_trait]
impl RequestInterceptor for CustomAuthInterceptor {
    async fn intercept(&self, request: &mut RestRequest) -> Result<()> {
        // Add custom headers, modify URL, etc.
        request.headers.insert(
            "X-Custom-Auth".to_string(),
            "custom-value".to_string()
        );
        Ok(())
    }
}

Retry Configuration

Configure automatic retries with exponential backoff:

rust
use grpc_graphql_gateway::{RestConnector, RetryConfig};
use std::time::Duration;

let connector = RestConnector::builder()
    .base_url("https://api.example.com")
    .retry(RetryConfig {
        max_retries: 3,
        initial_backoff: Duration::from_millis(100),
        max_backoff: Duration::from_secs(10),
        multiplier: 2.0,
        retry_statuses: vec![429, 500, 502, 503, 504],
    })
    .build()?;

Preset Configurations

rust
// Disable retries
RetryConfig::disabled()

// Aggressive retries for critical endpoints
RetryConfig::aggressive()

Response Caching

Enable LRU caching for GET requests:

rust
let connector = RestConnector::builder()
    .base_url("https://api.example.com")
    .with_cache(1000)  // Cache up to 1000 responses
    .build()?;

// Clear cache manually
connector.clear_cache().await;

Cache keys are based on endpoint name + arguments.

Multiple Connectors

Register multiple REST connectors for different services:

rust
let users_api = RestConnector::builder()
    .base_url("https://users.example.com")
    .add_endpoint(RestEndpoint::new("getUser", "/users/{id}"))
    .build()?;

let products_api = RestConnector::builder()
    .base_url("https://products.example.com")
    .add_endpoint(RestEndpoint::new("getProduct", "/products/{id}"))
    .build()?;

let orders_api = RestConnector::builder()
    .base_url("https://orders.example.com")
    .add_endpoint(RestEndpoint::new("getOrder", "/orders/{id}"))
    .build()?;

let gateway = Gateway::builder()
    .add_rest_connector("users", users_api)
    .add_rest_connector("products", products_api)
    .add_rest_connector("orders", orders_api)
    .build()?;

Executing Endpoints

Execute endpoints programmatically:

rust
use std::collections::HashMap;
use serde_json::json;

let mut args = HashMap::new();
args.insert("id".to_string(), json!("123"));

let result = connector.execute("getUser", args).await?;

Custom Response Transformer

Transform responses before returning to GraphQL:

rust
use grpc_graphql_gateway::{ResponseTransformer, RestResponse, Result};
use async_trait::async_trait;
use serde_json::Value as JsonValue;
use std::sync::Arc;

struct SnakeToCamelTransformer;

#[async_trait]
impl ResponseTransformer for SnakeToCamelTransformer {
    async fn transform(&self, endpoint: &str, response: RestResponse) -> Result<JsonValue> {
        // Transform snake_case keys to camelCase
        Ok(transform_keys(response.body))
    }
}

let connector = RestConnector::builder()
    .base_url("https://api.example.com")
    .transformer(Arc::new(SnakeToCamelTransformer))
    .build()?;

Use Cases

Scenario Description
Hybrid Architecture Mix gRPC and REST backends in one GraphQL API
Gradual Migration Migrate from REST to gRPC incrementally
Third-Party APIs Integrate external REST APIs (Stripe, Twilio, etc.)
Legacy Systems Bridge legacy REST services with modern infrastructure
Multi-Protocol Support teams using different backend technologies

Best Practices

  1. Set Appropriate Timeouts: Use shorter timeouts for internal services, longer for external APIs.

  2. Enable Retries for Idempotent Operations: GET, PUT, DELETE are typically safe to retry.

  3. Use Response Extraction: Extract only needed data with response_path to reduce payload size.

  4. Cache Read-Heavy Endpoints: Enable caching for frequently-accessed, rarely-changing data.

  5. Secure Credentials: Use environment variables for API keys and tokens, not hardcoded values.

  6. Log Bodies in Development Only: Enable log_bodies only in development to avoid leaking sensitive data.

See Also

↑ ↓ navigate ↵ openDocumentation, at your fingertips.