Skip to content

Federation Gateway

The fraiseql federation gateway command federates multiple FraiseQL subgraphs into a single endpoint without Apollo Router.

Built-in GatewayApollo Router
Best forFraiseQL-only subgraphsMixed subgraphs (FraiseQL + non-FraiseQL)
DependenciesNone — single fraiseql binarySeparate binary + Rover CLI
Query planningType-level routing (HashMap)Field-level routing (query planner)
@shareable fieldsNot supportedSupported
@requires / @providesNot supportedSupported
SubscriptionsNot forwarded (use direct subgraph WebSocket)Supported

Use the built-in gateway when:

  • All subgraphs are FraiseQL services
  • Each type is fully owned by one subgraph (no @shareable fields)
  • You want zero external dependencies

Use Apollo Router when:

  • You have non-FraiseQL subgraphs (Node.js, Java, etc.)
  • You need field-level splitting or @requires / @provides
Built-in fraiseql federation gateway with type-level routing to subgraphs Built-in fraiseql federation gateway with type-level routing to subgraphs
Type-level routing via HashMap lookup — each type is fully owned by one subgraph.

FraiseQL’s CQRS architecture makes query planning simple: each type is a single JSONB view owned by exactly one subgraph. The gateway routes at the type level (a HashMap lookup), not the field level. This eliminates the complexity of general-purpose query planners.

  1. Start two FraiseQL subgraphs with federation enabled:
Terminal window
# Terminal 1: User service
cd user-service && fraiseql run --port 4001
# Terminal 2: Order service
cd order-service && fraiseql run --port 4002
  1. Create gateway.toml:
gateway.toml
[gateway]
port = 4000
bind = "0.0.0.0"
[[gateway.subgraphs]]
name = "users"
url = "http://localhost:4001/graphql"
[[gateway.subgraphs]]
name = "orders"
url = "http://localhost:4002/graphql"
  1. Start the gateway:
Terminal window
fraiseql federation gateway gateway.toml
  1. Query the gateway:
Terminal window
curl http://localhost:4000/graphql \
-H "Content-Type: application/json" \
-d '{"query":"{ users(limit: 5) { id name } }"}'

The gateway uses a separate config file from fraiseql.toml.

FieldTypeDefaultDescription
portinteger4000Gateway listen port
bindstring"0.0.0.0"Bind address
FieldTypeDefaultDescription
namestringrequiredSubgraph identifier
urlstringrequiredSubgraph GraphQL endpoint URL
FieldTypeDefaultDescription
failure_thresholdinteger5Failures before circuit opens
recovery_timeout_secsinteger30Seconds before half-open retry
FieldTypeDefaultDescription
sdl_ttl_secsinteger300Cache subgraph SDL for this duration
query_plan_cache_sizeinteger1000Max cached query plans
  1. Parse incoming GraphQL query
  2. Look up owning subgraph for each root type (type → subgraph map built from SDL introspection at startup)
  3. Fan out per-subgraph queries in parallel
  4. Collect responses
  5. Scan for @key UUID references to other subgraphs
  6. Batch _entities calls to resolve cross-subgraph references
  7. Stitch into final response

The type → subgraph map is a HashMap<TypeName, SubgraphUrl>. For FraiseQL services where each type is owned by exactly one subgraph, this is all the query planner needs.

When a type in one subgraph references an entity in another (e.g., Order.user_idUser.id), the gateway uses _entities batching:

# Client sends:
query {
orders(limit: 10) {
id
total
user { id name email }
}
}
# Gateway:
# 1. Fetch orders from order-service → gets user_id UUIDs
# 2. Batch _entities call to user-service:
# query { _entities(representations: [{__typename: "User", id: "..."}]) { id name email } }
# 3. Stitch user objects into order responses

The gateway reuses HttpEntityResolver for entity resolution — the same component FraiseQL subgraphs use internally, with SSRF protection, retry, circuit breaker, and request deduplication.

The gateway propagates W3C TraceContext headers (traceparent, tracestate) to all subgraph requests. Gateway spans appear as parent spans in your tracing backend (Jaeger, Grafana Tempo, etc.).

Configure tracing on the gateway via environment variables:

Terminal window
OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317 \
OTEL_SERVICE_NAME=fraiseql-gateway \
fraiseql federation gateway gateway.toml
  • No field-level splitting: @shareable fields across subgraphs are not supported. Each type must be fully owned by one subgraph.
  • No @requires / @provides: Pre-fetching fields from another subgraph before resolving a local field is not implemented.
  • No subscription forwarding: Use direct WebSocket connections to individual subgraph URLs for subscriptions.
  • FraiseQL-only: Mixed subgraphs (FraiseQL + non-FraiseQL) require Apollo Router.

If you currently use Apollo Router with FraiseQL-only subgraphs:

supergraph.yaml
federation_version: =2.4.0
subgraphs:
users:
routing_url: http://user-service:4001/graphql
schema:
subgraph_url: http://user-service:4001/graphql
orders:
routing_url: http://order-service:4002/graphql
schema:
subgraph_url: http://order-service:4002/graphql

What changes:

  • Replace supergraph.yaml + router.yaml with a single gateway.toml
  • Replace rover supergraph compose + router commands with fraiseql federation gateway
  • No supergraph.graphql artifact needed — the gateway introspects subgraphs at startup

What stays the same:

  • Per-service fraiseql.toml and schemas — no changes needed
  • JWT forwarding — the gateway passes Authorization headers to all subgraphs
  • Circuit breaker behavior