GraphQL
Schema Stitching and Federation
Schema Stitching and Federation
Learn how to combine multiple GraphQL schemas into a unified API using schema stitching and Apollo Federation.
Schema Stitching Concept
Schema stitching allows you to combine multiple GraphQL schemas into a single unified schema:
const { stitchSchemas } = require('@graphql-tools/stitch');
const { makeExecutableSchema } = require('@graphql-tools/schema');
// User service schema
const userSchema = makeExecutableSchema({
typeDefs: `
type User {
id: ID!
name: String!
email: String!
}
type Query {
user(id: ID!): User
users: [User!]!
}
`,
resolvers: {
Query: {
user: (_, { id }) => getUserById(id),
users: () => getAllUsers(),
},
},
});
// Product service schema
const productSchema = makeExecutableSchema({
typeDefs: `
type Product {
id: ID!
name: String!
price: Float!
}
type Query {
product(id: ID!): Product
products: [Product!]!
}
`,
resolvers: {
Query: {
product: (_, { id }) => getProductById(id),
products: () => getAllProducts(),
},
},
});
// Stitch schemas together
const stitchedSchema = stitchSchemas({
subschemas: [userSchema, productSchema],
});
Apollo Federation
Apollo Federation is a more sophisticated approach to distributed GraphQL architecture:
// User subgraph (users-service)
const { ApolloServer, gql } = require('apollo-server');
const { buildSubgraphSchema } = require('@apollo/subgraph');
const typeDefs = gql`
type User @key(fields: "id") {
id: ID!
name: String!
email: String!
}
extend type Query {
user(id: ID!): User
users: [User!]!
}
`;
const resolvers = {
Query: {
user: (_, { id }) => getUserById(id),
users: () => getAllUsers(),
},
User: {
__resolveReference(user) {
return getUserById(user.id);
},
},
};
const server = new ApolloServer({
schema: buildSubgraphSchema({ typeDefs, resolvers }),
port: 4001,
});
server.listen().then(({ url }) => {
console.log(`Users service ready at ${url}`);
});
Subgraphs and Supergraph
Define entity relationships across subgraphs:
// Order subgraph (orders-service)
const typeDefs = gql`
type Order @key(fields: "id") {
id: ID!
userId: ID!
user: User!
items: [OrderItem!]!
total: Float!
status: OrderStatus!
}
type OrderItem {
productId: ID!
product: Product!
quantity: Int!
price: Float!
}
enum OrderStatus {
PENDING
PROCESSING
SHIPPED
DELIVERED
CANCELLED
}
# Extend User from users-service
extend type User @key(fields: "id") {
id: ID! @external
orders: [Order!]!
}
# Extend Product from products-service
extend type Product @key(fields: "id") {
id: ID! @external
}
extend type Query {
order(id: ID!): Order
orders: [Order!]!
}
`;
const resolvers = {
Query: {
order: (_, { id }) => getOrderById(id),
orders: () => getAllOrders(),
},
Order: {
__resolveReference(order) {
return getOrderById(order.id);
},
user(order) {
return { __typename: 'User', id: order.userId };
},
},
User: {
orders(user) {
return getOrdersByUserId(user.id);
},
},
OrderItem: {
product(item) {
return { __typename: 'Product', id: item.productId };
},
},
};
Note: The
@key directive defines the primary key for an entity. The @external directive marks fields that are defined in another subgraph.
Entity Resolvers
Implement reference resolvers to fetch entities across services:
// Product subgraph with entity resolver
const typeDefs = gql`
type Product @key(fields: "id") {
id: ID!
name: String!
description: String!
price: Float!
stock: Int!
}
extend type Query {
product(id: ID!): Product
products: [Product!]!
}
`;
const resolvers = {
Query: {
product: (_, { id }) => getProductById(id),
products: () => getAllProducts(),
},
Product: {
// This resolver is called when another service requests this entity
__resolveReference(reference) {
return getProductById(reference.id);
},
},
};
Gateway Setup
Configure Apollo Gateway to combine all subgraphs:
const { ApolloServer } = require('apollo-server');
const { ApolloGateway, IntrospectAndCompose } = require('@apollo/gateway');
// Define subgraph endpoints
const gateway = new ApolloGateway({
supergraphSdl: new IntrospectAndCompose({
subgraphs: [
{ name: 'users', url: 'http://localhost:4001/graphql' },
{ name: 'products', url: 'http://localhost:4002/graphql' },
{ name: 'orders', url: 'http://localhost:4003/graphql' },
],
}),
});
const server = new ApolloServer({
gateway,
subscriptions: false,
});
server.listen({ port: 4000 }).then(({ url }) => {
console.log(`Gateway ready at ${url}`);
});
Tip: Use managed federation with Apollo Studio for automatic schema composition and validation in production environments.
Supergraph Configuration File
Define your federation architecture declaratively:
# supergraph-config.yaml
federation_version: 2
subgraphs:
users:
routing_url: http://users-service:4001/graphql
schema:
file: ./users-schema.graphql
products:
routing_url: http://products-service:4002/graphql
schema:
file: ./products-schema.graphql
orders:
routing_url: http://orders-service:4003/graphql
schema:
file: ./orders-schema.graphql
# Generate supergraph schema
rover supergraph compose --config supergraph-config.yaml > supergraph.graphql
# Start gateway with composed schema
const { readFileSync } = require('fs');
const gateway = new ApolloGateway({
supergraphSdl: readFileSync('./supergraph.graphql').toString(),
});
Warning: Circular dependencies between subgraphs can cause resolution issues. Design your entity relationships carefully to avoid infinite loops.
Testing Federation Queries
Query across multiple subgraphs seamlessly:
query GetUserWithOrders {
user(id: "1") {
id
name
email
orders {
id
total
status
items {
quantity
price
product {
id
name
price
}
}
}
}
}
Exercise:
- Create three subgraphs (users, products, orders) with Apollo Federation
- Define entity relationships using
@keyandextenddirectives - Implement reference resolvers for cross-service queries
- Set up an Apollo Gateway to combine all subgraphs
- Test a query that spans multiple services