GraphQL Federation: Unified Schema Across Microservices
What is Federation?
Without Federation:
Client -> Each service has its own GraphQL endpoint
Client -> /api/users/graphql
Client -> /api/orders/graphql
Client -> /api/products/graphql
With Federation:
Client -> Single Gateway -> Routes to appropriate subgraphs
Unified schema: user has orders has products
Subgraph Setup
// users-service/schema.ts
import { buildSubgraphSchema } from '@apollo/subgraph';
import { gql } from 'graphql-tag';
const typeDefs = gql`
extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@shareable"])
type User @key(fields: "id") {
id: ID!
email: String!
name: String!
profile: UserProfile
}
type UserProfile {
bio: String
avatarUrl: String
}
type Query {
me: User
user(id: ID!): User
}
`;
const resolvers = {
Query: {
me: (_, __, { userId }) => userRepo.findById(userId),
user: (_, { id }) => userRepo.findById(id),
},
User: {
// Reference resolver: allows other subgraphs to extend User
__resolveReference(reference: { id: string }) {
return userRepo.findById(reference.id);
},
profile: (user) => userProfileRepo.findByUserId(user.id),
},
};
export const schema = buildSubgraphSchema({ typeDefs, resolvers });
// orders-service/schema.ts
const typeDefs = gql`
extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key", "@extends"])
# Extend User type from users-service
type User @key(fields: "id") {
id: ID! @external
orders(first: Int): OrderConnection!
}
type Order @key(fields: "id") {
id: ID!
status: OrderStatus!
total: Float!
createdAt: String!
items: [OrderItem!]!
}
type Query {
order(id: ID!): Order
}
`;
const resolvers = {
User: {
orders: (user, { first = 10 }) => orderRepo.findByUserId(user.id, { limit: first }),
},
Order: {
__resolveReference(ref: { id: string }) {
return orderRepo.findById(ref.id);
},
},
};
Apollo Router (Gateway)
# router.yaml
supergraph:
listen: 0.0.0.0:4000
subgraphs:
users:
routing_url: http://users-service:4001/graphql
orders:
routing_url: http://orders-service:4002/graphql
products:
routing_url: http://products-service:4003/graphql
# Compose supergraph schema
rover supergraph compose --config supergraph.yaml > supergraph.graphql
# Start router
./router --config router.yaml --supergraph supergraph.graphql
Schema Composition
# Resulting unified schema (auto-composed by Apollo Router)
type User {
id: ID!
email: String!
name: String!
profile: UserProfile # From users-service
orders(first: Int): OrderConnection! # From orders-service
recommendations: [Product!]! # From products-service
}
Client Query (spans multiple services)
query GetUserDashboard($userId: ID!) {
user(id: $userId) {
name
email
# Resolved by users-service
profile {
avatarUrl
}
# Resolved by orders-service
orders(first: 5) {
edges {
node {
id
status
total
}
}
}
}
}
# Apollo Router automatically:
# 1. Fetches user from users-service
# 2. Fetches orders using user.id from orders-service
# 3. Merges and returns combined result
Schema Registry
# Publish subgraph schemas to Apollo Studio
rover subgraph publish my-graph@prod --name users --schema users-service/schema.graphql --routing-url https://users-service.internal/graphql
# Check for breaking changes before publishing
rover subgraph check my-graph@prod --name users --schema users-service/schema.graphql
Federation enables teams to own their GraphQL schemas independently while presenting a unified API.