Apollo Federation: Stitch Two Subgraphs into a Single Gateway
Learn how Apollo Federation stitches independent GraphQL services into a single schema, with a step‑by‑step example, common pitfalls, and verification tips.
20 Dec 2025, 14:08 UTC

Why Use Apollo Federation?
Apollo Federation lets you keep each micro‑service’s GraphQL schema isolated while still presenting a unified API to your clients. The gateway stitches the schemas together by resolving entity references across services.
Core Concepts
- @key – Declares the primary identifier for an entity that lives in a subgraph.
- @provides – Indicates that a field in one subgraph can supply data for a field in another.
- @requires – Declares that a field depends on data from another subgraph.
- @external – Marks a field as defined in another subgraph.
Step‑by‑Step Example
We’ll create two minimal subgraphs: user-service and post-service. They will share the User entity. The gateway will then expose a single endpoint to the client.
1. Setup the User Service (Subgraph A)
// user-service/package.json
{
"name": "user-service",
"version": "1.0.0",
"dependencies": {
"apollo-server": "^4.0.0",
"graphql": "^16.0.0"
}
}
// user-service/index.js
const { ApolloServer, gql } = require('apollo-server');
const typeDefs = gql`
type User @key(fields: "id") {
id: ID!
name: String
}
extend type Query {
user(id: ID!): User
}
`;
const resolvers = {
Query: {
user: (_, { id }) => ({ id, name: `User ${id}` })
},
User: {
__resolveReference: ({ id }) => ({ id, name: `User ${id}` })
}
};
const server = new ApolloServer({ typeDefs, resolvers });
server.listen({ port: 4001 }).then(({ url }) => console.log(`User service ready at ${url}`));
2. Setup the Post Service (Subgraph B)
// post-service/package.json
{
"name": "post-service",
"version": "1.0.0",
"dependencies": {
"apollo-server": "^4.0.0",
"graphql": "^16.0.0"
}
}
// post-service/index.js
const { ApolloServer, gql } = require('apollo-server');
const typeDefs = gql`
type Post @key(fields: "id") {
id: ID!
title: String
author: User @provides(fields: "name")
}
extend type Query {
post(id: ID!): Post
}
`;
const resolvers = {
Query: {
post: (_, { id }) => ({ id, title: `Post ${id}` })
},
Post: {
__resolveReference: ({ id }) => ({ id, title: `Post ${id}` })
}
};
const server = new ApolloServer({ typeDefs, resolvers });
server.listen({ port: 4002 }).then(({ url }) => console.log(`Post service ready at ${url}`));
3. Create the Gateway
// gateway/package.json
{
"name": "gateway",
"version": "1.0.0",
"dependencies": {
"@apollo/gateway": "^2.0.0",
"apollo-server": "^4.0.0",
"graphql": "^16.0.0"
}
}
// gateway/index.js
const { ApolloServer } = require('apollo-server');
const { ApolloGateway } = require('@apollo/gateway');
const gateway = new ApolloGateway({
serviceList: [
{ name: 'user', url: 'http://localhost:4001/graphql' },
{ name: 'post', url: 'http://localhost:4002/graphql' }
]
});
const server = new ApolloServer({ gateway, subscriptions: false });
server.listen({ port: 4000 }).then(({ url }) => console.log(`Gateway ready at ${url}`));
4. Verify the Composition
Run each service and the gateway in separate terminals:
- npm run start in
user-service(port 4001) - npm run start in
post-service(port 4002) - npm run start in
gateway(port 4000)
Open the Gateway’s GraphQL Playground at http://localhost:4000 and execute:
query {
_entities(representations: [{ __typename: "User", id: "1" }]) {
... on User {
id
name
}
}
}
The response should include the name field resolved by the User subgraph. You can also query a Post that references a User:
query {
_entities(representations: [{ __typename: "Post", id: "10" }]) {
... on Post {
id
title
author {
name
}
}
}
}
Logs from each subgraph will show that the gateway routed the entity resolution to the appropriate service.
Common Pitfalls
- Version Mismatch – The gateway and subgraphs must use compatible Apollo Server and @apollo/gateway releases. Mixing 3.x subgraphs with a 4.x gateway can break schema composition.
- Missing @key – Every federated entity requires a @key. Forgetting it results in a schema error during gateway startup.
- Exposing Introspection – The gateway’s
/graphqlendpoint exposes the entire schema. In production, secure it behind authentication or disable introspection. - Circular Dependencies – Two subgraphs that reference each other’s entities in a loop cause the gateway to hang. Avoid or break the cycle with
@providesand@requirescarefully. - Unupdated Directives – Adding a field that depends on another subgraph’s field requires updating
@requiresor@providesaccordingly.
Performance Considerations
- Each entity resolution may involve a round‑trip to the owning subgraph. Use the gateway’s built‑in
DataLoadercaching to batch multiple references into a single request. - Enable
debug: falsein production to reduce logging overhead. - Monitor the gateway’s
executionTimeandentityResolutionTimemetrics to spot bottlenecks.
Practical Checklist
- Define a unique
@keyfor every federated type. - Keep subgraphs and the gateway on the same major Apollo Server version.
- Test the gateway locally with
graphql-playgroundbefore deploying. - Secure introspection in production.
- Use
__resolveReferenceresolvers that return the minimal fields required by other subgraphs. - Enable caching in the gateway to mitigate round‑trip latency.
Conclusion
Apollo Federation provides a clean, declarative way to compose independent GraphQL services. By following the example above, you can set up a gateway that stitches two subgraphs, verify the composite schema, and avoid common pitfalls. Remember to keep versions aligned, secure introspection, and monitor performance to maintain a healthy federated architecture.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.