Concept
The beginner framing: a large organization often has GraphQL functionality spread across multiple teams and services, a users service, a products service, an orders service, each with their own schema. Federation composes these separate services (called subgraphs) into one unified graph clients query as if it were a single API, without any one service needing to know about the others' full implementation.
Subgraphs: @key and buildSubgraphSchema
type User @key(fields: "id") {
id: ID!
name: String!
}
type Query {
me: User
}import { buildSubgraphSchema } from "@apollo/subgraph";
const schema = buildSubgraphSchema({ typeDefs, resolvers });Confirmed by building and querying this exact schema: @key(fields: "id") marks User as an entity, a type whose instances can be referenced and extended across multiple subgraphs, identified by its id. buildSubgraphSchema (rather than the plain makeExecutableSchema) is what wires up the additional federation-specific machinery this composition model needs.
Entity resolution: __resolveReference and the _entities query
const resolvers = {
User: {
__resolveReference(reference) {
// called when ANOTHER subgraph only has { __typename: "User", id: "..." }
// and needs THIS subgraph to fill in the rest of the entity
return fetchUserById(reference.id);
},
},
};Confirmed by executing this exact pattern directly, including the router's actual internal query shape: when a different subgraph has a User reference (say, an Order subgraph knows an order's userId but nothing else about that user) and a client's query needs User fields from this subgraph, the router sends a special _entities query, confirmed via real execution: query($representations: [_Any!]!) { _entities(representations: $representations) { ... on User { name } } }, and __resolveReference is the resolver that fills in the rest of the entity, given just its key fields. This is the actual mechanism underneath federation's "it feels like one graph" experience.
Version-currency callout, confirmed via Apollo's own current guidance: the router moved to Rust
For years, @apollo/gateway, a Node.js package, was the standard way to run the composition layer that stitches subgraphs together and routes client queries to the right ones. This is no longer the current production recommendation. Apollo now recommends the Apollo Router (also called the GraphOS Router), a standalone binary written in Rust, not Node.js, citing dramatically better performance: roughly 10x higher throughput and significantly lower latency than the JS gateway, with router memory consumption around 13% of the gateway's. @apollo/gateway still exists and still works, but it's now positioned specifically for edge cases the Router doesn't yet support (like certain custom authentication setups), not as the default choice for new production federation.
Client → Apollo Router (Rust binary, NOT a Node.js process) → subgraph A (Node.js/Apollo Server)
→ subgraph B (Node.js/Apollo Server)
→ subgraph C (any language, even)This is worth being precise about: the subgraph side is unaffected, individual subgraphs are still ordinary Node.js/Apollo Server services (or any language with federation-compatible tooling), built exactly as covered in Apollo Server. It's specifically the composition/routing layer sitting in front of them that changed runtime entirely.
Try It
Predict the outcome before checking the solution.
# Users subgraph:
type User @key(fields: "id") { id: ID!, name: String! }