GraphQL Federation â Distributed GraphQL for Microservices
In this tutorial, you will learn about GraphQL Federation. We cover key concepts, practical examples, and best practices to help you master this topic.
GraphQL Federation is an architecture pattern for composing a single GraphQL schema from multiple Microservices, each owning a portion of the schema.
What You'll Learn
You will learn how federation splits schemas across services, implement subgraphs with Apollo Federation, use the Gateway to compose them, and understand entity references and resolvers.
Why Federation Matters
Monolithic GraphQL servers don't scale as teams grow â one team's schema changes can break another team's queries. Federation lets each microservice own its part of the schema. DodaTech's Durga Antivirus Pro uses three federated services: Devices (device management), Threats (threat intelligence), and Users (authentication and profiles). Each team deploys independently, and the Gateway composes everything into one seamless API.
flowchart TB
Client --> Gateway["Apollo Gateway"]
Gateway --> Devices["Devices Service\n/devices/graphql"]
Gateway --> Threats["Threats Service\n/threats/graphql"]
Gateway --> Users["Users Service\n/users/graphql"]
Gateway --> Scans["Scans Service\n/scans/graphql"]
Devices --> DB1[(Devices DB)]
Threats --> DB2[(Threat Intelligence)]
Users --> DB3[(User Profiles)]
Scans --> DB4[(Scan Results)]
style Gateway fill:#dbeafe,stroke:#2563eb
style Devices fill:#fef3c7,stroke:#d97706
style Threats fill:#fef3c7,stroke:#d97706
style Users fill:#fef3c7,stroke:#d97706
style Scans fill:#fef3c7,stroke:#d97706
Prerequisites: GraphQL architecture. Microservices concepts. Apollo Server experience.
Defining a Subgraph (Devices Service)
# devices.graphql â Devices service schema
extend schema
@link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@shareable"])
type Device @key(fields: "id") {
id: ID!
name: String!
os: String!
version: String
userId: ID!
}
type Query {
devices: [Device!]!
device(id: ID!): Device
}
const { ApolloServer, gql } = require('apollo-server');
const { buildSubgraphSchema } = require('@apollo/subgraph');
const typeDefs = gql`
extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@shareable"])
type Device @key(fields: "id") {
id: ID!
name: String!
os: String!
version: String
userId: ID!
}
type Query {
devices: [Device!]!
device(id: ID!): Device
}
`;
const resolvers = {
Query: {
devices: () => db.devices.findAll(),
device: (_, { id }) => db.devices.findById(id),
},
// Reference resolver â resolves Device by ID for other services
Device: {
__resolveReference(ref) {
return db.devices.findById(ref.id);
},
},
};
const server = new ApolloServer({
schema: buildSubgraphSchema({ typeDefs, resolvers }),
});
server.listen(4001);
Extending a Type (Users Service)
# users.graphql â Users service extends Device with user data
extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@external", "@requires"])
type Device @key(fields: "id") {
id: ID! @external
userId: ID! @external
user: User! @requires(fields: "userId")
}
type User @key(fields: "id") {
id: ID!
name: String!
email: String!
role: String!
devices: [Device!]!
}
extend type Query {
users: [User!]!
user(id: ID!): User
}
const resolvers = {
Device: {
__resolveReference(ref) {
// Get the device's userId and fetch the user
return { id: ref.id, userId: ref.userId };
},
user(device) {
return db.users.findById(device.userId);
},
},
User: {
__resolveReference(ref) {
return db.users.findById(ref.id);
},
devices(user) {
return db.devices.findByUserId(user.id);
},
},
};
Setting Up the Gateway
const { ApolloGateway } = require('@apollo/gateway');
const { ApolloServer } = require('apollo-server');
const gateway = new ApolloGateway({
serviceList: [
{ name: 'devices', url: 'http://devices-service:4001/graphql' },
{ name: 'threats', url: 'http://threats-service:4002/graphql' },
{ name: 'users', url: 'http://users-service:4003/graphql' },
{ name: 'scans', url: 'http://scans-service:4004/graphql' },
],
});
const server = new ApolloServer({
gateway,
subscriptions: false, // Federation doesn't support subscriptions yet
context: ({ req }) => ({
user: authenticate(req),
}),
});
server.listen(4000).then(({ url }) => {
console.log(`Gateway ready at ${url}`);
});
# Client query â seamlessly spans all services
query DashboardData {
user(id: "user-001") {
name
email
devices { # From Users service
id
name
os # From Devices service
threats { # From Threats service
id
name
severity
}
scans { # From Scans service
id
status
completedAt
}
}
}
}
Entity Reference Resolver Pattern
// Each subgraph must implement __resolveReference for entities it owns
const resolvers = {
Device: {
__resolveReference(ref, context) {
// ref contains { __typename: "Device", id: "dev-001" }
return context.db.devices.findById(ref.id);
},
},
User: {
__resolveReference(ref) {
return context.db.users.findById(ref.id);
},
},
};
Federation Directives
# @key â declares an entity's primary key
type Device @key(fields: "id") { ... }
# @external â marks a field defined in another subgraph
type Device @key(fields: "id") {
id: ID! @external
userId: ID! @external
}
# @requires â declares that this field depends on external fields
type Device @key(fields: "id") {
id: ID! @external
userId: ID! @external
user: User! @requires(fields: "userId")
}
# @shareable â field can be resolved by multiple subgraphs
type Threat @key(fields: "id") {
id: ID!
name: String! @shareable
severity: Severity! @shareable
}
# @provides â subgraph can resolve this field for other subgraphs
type Device @key(fields: "id") {
id: ID!
threatSummary: ThreatSummary @provides(fields: "count")
}
Common Mistakes
1. Circular Entity References
If Service A extends Service B's type and Service B extends Service A's type, you get circular resolution. Break the cycle by making one side use @external.
2. Not Implementing __resolveReference
Without __resolveReference, the gateway cannot resolve entities across service boundaries. Every entity with @key needs this resolver.
3. Duplicating Field Definitions
Defining the same field in multiple subgraphs without @shareable causes schema composition errors. Use @shareable for fields that multiple services can resolve.
4. Ignoring Gateway Performance
Each query may trigger multiple network calls between the gateway and subgraphs. Add DataLoader in the gateway or use @requires to minimize calls.
5. Federation Without Proper Error Handling
A failing subgraph can take down the entire gateway. Implement circuit breakers, timeouts, and fallback responses in the gateway.
Practice Questions
- What problem does GraphQL Federation solve?
- What is the role of the Apollo Gateway?
- What does the @key directive do?
- How does __resolveReference work?
- What is the difference between @external and @shareable?
Answers:
- Federation enables multiple microservices to own parts of a single GraphQL schema. Each team deploys independently while the Gateway composes a unified API.
- The Gateway fetches the schema from each subgraph, composes them into one schema, and routes incoming queries to the appropriate subgraphs.
@key(fields: "id")declares an entity type and its primary key. Other subgraphs can reference this entity using the key fields.__resolveReference(ref)resolves an entity by its key fields when another subgraph references it. It receives{ __typename, id }and returns the full entity.@externalmarks a field that is defined in another subgraph.@shareablemarks a field that multiple subgraphs can resolve.
Challenge: Design a federated GraphQL architecture for DodaTech's full platform. Create subgraphs for Users (auth, profiles), Devices (management, inventory), Threats (detection, intelligence), Scans (scheduling, results), Alerts (notifications, routing), and Billing (plans, invoices). Implement the Gateway with proper error handling and Caching.
FAQ
Mini Project
Build a federated GraphQL API for DodaTech. Create three subgraphs: Devices (device management, @key on Device), Threats (threat detection, extends Device), and Users (user profiles, extends Device). Set up the Gateway with proper composition, implement __resolveReference on all entities, and build a client query that fetches user devices with their threats.
What's Next
| Topic | Description |
|---|---|
| Apollo Server | Production server configuration |
| Code Generation | TypeScript types from schema |
| Testing | Testing GraphQL APIs |
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro