Federation Gateway
Federation Gateway — Built-in gateway for FraiseQL-only setups
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.
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:
fraiseql.toml with a single [database] sectionschema.compiled.jsonFederation is a good fit when:
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.
fraiseql.tomlEvery FraiseQL service has exactly one database. Use the [database] section (singular — there is no [databases.service_name] multi-database configuration):
[project]name = "user-service"version = "1.0.0"
[fraiseql]schema_file = "schema.json"output_file = "schema.compiled.json"
[database]url = "${DATABASE_URL}"
[federation]enabled = trueapollo_version = 2
[[federation.entities]]name = "User"key_fields = ["id"][project]name = "order-service"version = "1.0.0"
[fraiseql]schema_file = "schema.json"output_file = "schema.compiled.json"
[database]url = "${DATABASE_URL}"
[federation]enabled = trueapollo_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 = trueapollo_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 fraiseqlfrom fraiseql.scalars import ID, DateTime
@fraiseql.typeclass User: """User record owned by the user service.""" id: ID name: str email: str created_at: DateTime
@fraiseql.querydef user(id: ID) -> User | None: """Fetch a user by ID.""" return fraiseql.config(sql_source="v_user")
@fraiseql.querydef 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"), )import fraiseqlfrom fraiseql.scalars import ID, DateTime, Decimal
@fraiseql.typeclass Order: """Order record owned by the order service.""" id: ID total: Decimal status: str created_at: DateTime
# Compound key example — most types just use the default ["id"]@fraiseql.type(key_fields=["id", "region"])class RegionalOrder: """An order with a compound federation key.""" id: ID region: str total: Decimal
@fraiseql.querydef order(id: ID) -> Order | None: """Fetch an order by ID.""" return fraiseql.config(sql_source="v_order")
@fraiseql.querydef orders(limit: int = 20, offset: int = 0) -> list[Order]: """List orders.""" return fraiseql.config(sql_source="v_order")
if __name__ == "__main__": fraiseql.export_schema( "schema.json", federation=fraiseql.Federation(service_name="order-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 fraiseqlfrom fraiseql.scalars import ID, DateTime
@fraiseql.typeclass User: """User record owned by the user service.""" id: ID name: str email: str created_at: DateTime
@fraiseql.querydef user(id: ID) -> User | None: """Fetch a user by ID.""" return fraiseql.config(sql_source="v_user")
@fraiseql.querydef users(limit: int = 20, offset: int = 0) -> list[User]: """List users.""" return fraiseql.config(sql_source="v_user")import fraiseqlfrom fraiseql.scalars import ID, DateTime, Decimal
@fraiseql.typeclass Order: """Order record owned by the order service.""" id: ID total: Decimal status: str created_at: DateTime
@fraiseql.querydef order(id: ID) -> Order | None: """Fetch an order by ID.""" return fraiseql.config(sql_source="v_order")
@fraiseql.querydef orders(limit: int = 20, offset: int = 0) -> list[Order]: """List orders.""" return fraiseql.config(sql_source="v_order")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:
supergraph: listen: 0.0.0.0:4000
# Forward the client's Authorization header to every subgraphheaders: all: request: - propagate: named: Authorization
subgraphs: users: routing_url: http://user-service:4001/graphql orders: routing_url: http://order-service:4002/graphqlCompose the supergraph schema with Rover CLI:
rover supergraph compose --config supergraph.yaml > supergraph.graphqlrouter --config router.yaml --supergraph supergraph.graphqlAfter 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:
| State | Behaviour |
|---|---|
| Closed | Normal operation — requests pass through |
| Open | Tripped after failure_threshold consecutive errors — requests rejected with 503 |
| HalfOpen | Recovery probe — success_threshold successes closes the circuit |
[federation.circuit_breaker]enabled = truefailure_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)| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | true | Enable the circuit breaker |
failure_threshold | integer | 5 | Consecutive errors before opening |
recovery_timeout_secs | integer | 30 | Seconds before attempting a probe |
success_threshold | integer | 2 | Successes in HalfOpen before closing |
Apply stricter thresholds to specific entities:
[[federation.circuit_breaker.per_database]]database = "Order" # name must match the federation entityfailure_threshold = 3 # stricter: open after 3 errorsrecovery_timeout_secs = 60Multiple [[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 = HalfOpenWhen a write on one service needs to trigger an action in another service, FraiseQL can integrate with NATS for durable cross-service messaging.
[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.
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:
_entities queries)/rest/* for services that prefer RESTgrpc-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:
@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.
Federation Gateway
Federation Gateway — Built-in gateway for FraiseQL-only setups
Federation Configuration
Federation Configuration — Full per-service TOML reference and Docker Compose example
Advanced Federation
Advanced Federation Patterns — Apollo Federation v2 subgraph architecture
Federation + NATS
Federation + NATS Integration — Cross-service event routing
Observers
Observers — React to cross-service changes