Skip to content

Federation

FraiseQL supports Apollo Federation v2. Each FraiseQL service operates as a standalone GraphQL subgraph — its own Python schema, its own compiled Rust runtime, and its own PostgreSQL database. A gateway composes the subgraphs into a single entry point for clients.

Federation architecture: client to gateway to subgraph services with separate databases Federation architecture: client to gateway to subgraph services with separate databases
Each service owns its own database — the gateway (fraiseql gateway or Apollo Router) composes them.
Section titled “Built-in Gateway (recommended for FraiseQL-only setups)”

FraiseQL includes a lightweight federation gateway (fraiseql gateway) optimized for CQRS architectures where each type is fully owned by one subgraph. No external dependencies required — type-level routing via a simple HashMap lookup.

See Federation Gateway Guide for setup.

When federating FraiseQL subgraphs with non-FraiseQL services (e.g., a Node.js subgraph alongside FraiseQL subgraphs), use Apollo Router as the gateway. Apollo Router supports field-level query planning, @shareable, @requires, and @provides.

Each FraiseQL service is fully standalone:

  • Its own fraiseql.toml with a single [database] section
  • Its own Python schema file compiled to schema.compiled.json
  • Its own Rust runtime process

Federation is a good fit when:

  • You have multiple teams where each team owns its own service and database
  • You are migrating from an Apollo Gateway and want to maintain subgraph boundaries
  • You need independent deployment of services without coordinating a combined release

If you have a single team with multiple databases and want a unified API without the operational overhead of Apollo Router, consider separate FraiseQL services each exposing their own endpoint instead.

Every FraiseQL service has exactly one database. Use the [database] section (singular — there is no [databases.service_name] multi-database configuration):

user-service/fraiseql.toml
[project]
name = "user-service"
version = "1.0.0"
[fraiseql]
schema_file = "schema.json"
output_file = "schema.compiled.json"
[database]
url = "${DATABASE_URL}"
[federation]
enabled = true
apollo_version = 2
[[federation.entities]]
name = "User"
key_fields = ["id"]
order-service/fraiseql.toml
[project]
name = "order-service"
version = "1.0.0"
[fraiseql]
schema_file = "schema.json"
output_file = "schema.compiled.json"
[database]
url = "${DATABASE_URL}"
[federation]
enabled = true
apollo_version = 2
[[federation.entities]]
name = "Order"
key_fields = ["id"]

The [federation] section enables Apollo Federation v2 and declares which entity types the service owns:

[federation]
enabled = true
apollo_version = 2
[[federation.entities]]
name = "User"
key_fields = ["id"]
[[federation.entities]]
name = "Order"
key_fields = ["id"]

There are two ways to declare federation entities: in the SDK (recommended) or in TOML.

Pass federation= to export_schema() and it auto-derives the entity list from your registered types. Every type defaults to key_fields=["id"] — you only need to set key_fields explicitly for compound keys.

import fraiseql
from fraiseql.scalars import ID, DateTime
@fraiseql.type
class User:
"""User record owned by the user service."""
id: ID
name: str
email: str
created_at: DateTime
@fraiseql.query
def user(id: ID) -> User | None:
"""Fetch a user by ID."""
return fraiseql.config(sql_source="v_user")
@fraiseql.query
def users(limit: int = 20, offset: int = 0) -> list[User]:
"""List users."""
return fraiseql.config(sql_source="v_user")
if __name__ == "__main__":
fraiseql.export_schema(
"schema.json",
federation=fraiseql.Federation(service_name="user-service"),
)

The exported schema.json includes a "federation" block with enabled: true and the auto-derived entity list. The Rust compiler reads this directly — no manual [[federation.entities]] TOML needed.

You can also declare entities manually in fraiseql.toml. This is useful when you use export_types() instead of export_schema(), or when federation config needs to live separately from the schema code.

import fraiseql
from fraiseql.scalars import ID, DateTime
@fraiseql.type
class User:
"""User record owned by the user service."""
id: ID
name: str
email: str
created_at: DateTime
@fraiseql.query
def user(id: ID) -> User | None:
"""Fetch a user by ID."""
return fraiseql.config(sql_source="v_user")
@fraiseql.query
def users(limit: int = 20, offset: int = 0) -> list[User]:
"""List users."""
return fraiseql.config(sql_source="v_user")

Entity resolution is configured entirely in TOML via [[federation.entities]]. FraiseQL automatically generates the _entities resolver required by the Apollo Federation spec — no additional Python code is needed. When the Apollo Router sends a _entities query with { __typename: "User", id: "..." }, FraiseQL resolves it using the view mapped to that entity type.

The Apollo Router composes subgraphs by URL. It reads a supergraph schema composed by Rover CLI:

router.yaml
supergraph:
listen: 0.0.0.0:4000
# Forward the client's Authorization header to every subgraph
headers:
all:
request:
- propagate:
named: Authorization
subgraphs:
users:
routing_url: http://user-service:4001/graphql
orders:
routing_url: http://order-service:4002/graphql

Compose the supergraph schema with Rover CLI:

Terminal window
rover supergraph compose --config supergraph.yaml > supergraph.graphql
router --config router.yaml --supergraph supergraph.graphql

After composing the supergraph, clients send queries to the Apollo Router. The router plans which subgraphs to query and merges responses:

query UserDashboard($userId: ID!, $orderId: ID!) {
user(id: $userId) {
id
name
email
}
order(id: $orderId) {
id
total
status
}
}

The user field is resolved by the user service. The order field is resolved by the order service. Apollo Router executes both in parallel and merges the result before sending it to the client.

When FraiseQL acts as a subgraph in an Apollo Federation setup, a per-entity circuit breaker prevents cascading failures. After a configured number of consecutive errors the circuit opens: further calls to that entity are rejected immediately with 503 Service Unavailable and a Retry-After header rather than waiting for a timeout.

The circuit moves through three states:

StateBehaviour
ClosedNormal operation — requests pass through
OpenTripped after failure_threshold consecutive errors — requests rejected with 503
HalfOpenRecovery probe — success_threshold successes closes the circuit
[federation.circuit_breaker]
enabled = true
failure_threshold = 5 # open after N consecutive errors (default: 5)
recovery_timeout_secs = 30 # probe after this many seconds (default: 30)
success_threshold = 2 # successes in HalfOpen needed to close (default: 2)
FieldTypeDefaultDescription
enabledbooltrueEnable the circuit breaker
failure_thresholdinteger5Consecutive errors before opening
recovery_timeout_secsinteger30Seconds before attempting a probe
success_thresholdinteger2Successes in HalfOpen before closing

Apply stricter thresholds to specific entities:

[[federation.circuit_breaker.per_database]]
database = "Order" # name must match the federation entity
failure_threshold = 3 # stricter: open after 3 errors
recovery_timeout_secs = 60

Multiple [[federation.circuit_breaker.per_database]] entries are allowed — each with its own database name.

FraiseQL exposes a Prometheus gauge for the circuit state of each entity:

fraiseql_federation_circuit_breaker_state{entity="Order"} 1
# 0 = Closed, 1 = Open, 2 = HalfOpen

When a write on one service needs to trigger an action in another service, FraiseQL can integrate with NATS for durable cross-service messaging.

order-service/fraiseql.toml
[observers]
backend = "nats"
nats_url = "${NATS_URL}"

When a mutation executes, FraiseQL publishes events to NATS via the observer system. The downstream service subscribes to these events independently of its own database operations.


REST and gRPC Alongside GraphQL Federation

Section titled “REST and gRPC Alongside GraphQL Federation”

Each FraiseQL federation subgraph also serves REST on the same port 8080. gRPC is available when FraiseQL is built with the grpc-transport Cargo feature (not included in the official Docker image — see gRPC Transport). These transports are independent of the Apollo Federation protocol — they expose the same data and mutations through different wire formats. The Apollo Router only federates GraphQL traffic; REST and gRPC requests go directly to individual subgraph URLs.

This means a single subgraph can simultaneously:

  • Participate in GraphQL federation (Apollo Router routes _entities queries)
  • Serve a REST API at /rest/* for services that prefer REST
  • Serve a gRPC API (HTTP/2) for high-throughput batch consumers (requires grpc-transport feature)

REST is enabled by default on every FraiseQL service, including those with [federation] enabled = true. gRPC requires building from source with --features grpc-transport.

Apollo Federation with FraiseQL has these limitations:

  1. No cross-service two-phase commit: Each service operation is independent. There is no distributed two-phase commit. For cross-service consistency, use the saga pattern — SagaExecutor with SagaCompensator and PostgresSagaStore provides production-ready orchestration with crash recovery and automatic compensation on failure.
  2. No cross-service SQL joins: Each service queries its own database only. Field resolution across services happens at the Apollo Router level.
  3. Each service has one database: FraiseQL does not support connecting a single service to multiple databases simultaneously. Split data ownership across services instead.
  4. @requires needs Apollo Router for cross-service resolution: FraiseQL implements @requires at the subgraph level (compile-time validation via RequiresProvidesValidator, runtime entity resolution with circuit breaker). However, the built-in gateway does not perform field-level query planning — use Apollo Router if your supergraph composition depends on cross-service @requires / @provides resolution.

Each service owns its domain. Define entity types only in the service that owns the underlying database table. Reference types from other services via the Apollo Federation @key directive in the composed supergraph.

Forward JWTs through the router. Configure Apollo Router to propagate the Authorization header to all subgraphs so each FraiseQL service can independently validate the JWT and apply its own authorization policies.

Monitor circuit breaker state. Use the Prometheus metric fraiseql_federation_circuit_breaker_state to alert when any subgraph’s circuit opens in production.

Observers

Observers — React to cross-service changes