System DesignNov 6, 2026⏱️ 4 min read
Federated GraphQL & Apollo Supergraphs: Composing Distributed Microservices into a Single API
System DesignGraphQLMicroservicesApollo FederationArchitecture

Federated GraphQL & Apollo Supergraphs: Composing Distributed Microservices into a Single API

The Monolithic GraphQL Bottleneck

In the early days of GraphQL adoption, companies built a single monolithic GraphQL server. All schemas, types, and resolvers lived in one central repository.
As engineering teams expanded from 5 developers to 100+ developers across autonomous squads, this monolithic setup became a severe liability:
  • Merge Conflicts & Broken Deployments: Every team touched the same schema files. A syntax error committed by the payments team blocked releases for the authentication and catalog teams.
  • Single Point of Failure: If the monolithic GraphQL server crashed, every client application went down.
  • Loss of Domain Ownership: Microservices are supposed to own their own data and lifecycles. Forcing them through a monolithic intermediary destroyed team autonomy.
To solve this, the enterprise standard transitioned to Federated GraphQL (Apollo Federation 2).

Anatomy of a Supergraph: Federation 2 Architecture

Instead of one monolithic schema, Federation divides the graph into Subgraphs owned by individual microservices, and a high-performance Supergraph Router that sits at the edge:
[Web & Mobile Clients]
         │  (Single GraphQL Request)
         ▼
[Apollo Router Gateway (Written in Rust)]
         │  (Query Planning & Subgraph Composition)
         ├───────────────────┼───────────────────┐
         ▼                   ▼                   ▼
  [Users Subgraph]    [Orders Subgraph]   [Products Subgraph]
  (Python / FastAPI)    (Node.js / TS)      (Go / Rust)
Key principles of Apollo Federation 2:
  1. Subgraphs: Independent GraphQL services that define schemas for their own domain.
  2. Entities (@key): Types that can be referenced and extended across multiple subgraphs (e.g., a User type defined in the Auth service, but extended with an orders field by the Orders service).
  3. Supergraph Schema: The compiled composition of all subgraphs.
  4. Apollo Router: A blazingly fast, multi-threaded binary written in Rust that receives client queries, plans an execution graph, and fetches data in parallel across downstream subgraphs.

Hands-On: Extending Entities Across Subgraphs

Let's look at how two completely separate services share and extend an entity without tight coupling.

1. The Users Subgraph (Auth Service)

The Users service defines the core User entity and declares its primary key using @key(fields: "id"):
graphql
# users-subgraph.graphql extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"]) type User @key(fields: "id") { id: ID! email: String! username: String! } type Query { me: User }

2. The Orders Subgraph (E-Commerce Service)

The Orders service doesn't own user emails or passwords, but it needs to add order history to a user. It references the User entity stub and enriches it:
graphql
# orders-subgraph.graphql extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"]) # Reference the User entity from the Users service type User @key(fields: "id") { id: ID! orders: [Order!]! # Enrich User with domain-specific fields! } type Order { id: ID! amount: Float! status: String! createdAt: String! }

The Resolver Implementation in Python (Strawberry GraphQL):

python
# orders_service/schema.py import strawberry from typing import List @strawberry.federation.type(keys=["id"]) class User: id: strawberry.ID @strawberry.field async def orders(self) -> List["Order"]: # Query orders database for this user id return await fetch_orders_for_user(self.id) @strawberry.type class Order: id: strawberry.ID amount: float status: str @strawberry.type class Query: pass schema = strawberry.federation.Schema(query=Query, types=[User, Order])

Client Queries & The Execution Plan

From the client's perspective, the distributed microservice architecture is completely invisible. The mobile app or web frontend issues a single unified query:
graphql
query GetUserProfileWithOrders { me { username # Fetched from Users Subgraph (FastAPI) email # Fetched from Users Subgraph (FastAPI) orders { # Fetched from Orders Subgraph (Python) id amount status } } }

What Apollo Router Does:

  1. Receives the incoming GraphQL document.
  2. Automatically generates an Execution Plan:
    • Step 1: Send query to Users Subgraph to resolve me { id, username, email }.
    • Step 2: Extract id from response and invoke Orders Subgraph with _entities(representations: [{ __typename: "User", id: $id }]).
    • Step 3: Stitch together the responses in RAM and return a single JSON object to the client in under 15ms.

Why Apollo Router (Rust) Replaced Apollo Gateway (Node.js)

In early Federation, Apollo Gateway ran as a Node.js process. Under high enterprise load (50,000+ RPS), Node.js suffered from event loop lag and garbage collection pauses.
Apollo rewritten the router in Rust:
  • 10x Lower Latency: P99 latency dropped from 35ms to under 3ms.
  • 12x Higher Throughput: Substantially higher request concurrency per CPU core.
  • Memory Footprint: Consumes a fraction of the RAM of Node.js instances.
By adopting Apollo Federation 2 and Apollo Router, enterprise engineering teams achieve the best of both worlds: complete autonomy for microservice squads combined with a seamless, unified graph experience for frontend applications.

Share this article

Comments