GraphQL

Schema Stitching and Federation

20 min Lesson 26 of 35

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:
  1. Create three subgraphs (users, products, orders) with Apollo Federation
  2. Define entity relationships using @key and extend directives
  3. Implement reference resolvers for cross-service queries
  4. Set up an Apollo Gateway to combine all subgraphs
  5. Test a query that spans multiple services