GraphQL Federation is a powerful technique that allows you to compose multiple GraphQL services into a single, unified API. This approach is particularly useful in microservices architectures, where different teams or services manage different parts of the schema. Federation helps in scaling GraphQL across large organizations by enabling a modular and maintainable schema design.
Key Concepts
- Federated Services
Federated services are individual GraphQL services that expose part of the overall schema. Each service is responsible for a specific domain or functionality.
- Gateway
The gateway is a central service that aggregates the schemas from all federated services and provides a unified API to the clients. It handles query planning and execution across the federated services.
- Schema Composition
Schema composition is the process of merging schemas from multiple federated services into a single schema. This involves resolving conflicts and ensuring that the combined schema is valid.
- Type Extensions
Type extensions allow federated services to extend types defined in other services. This is useful for adding fields or relationships that span multiple services.
- Entities and Keys
Entities are types that can be referenced across services. Keys are fields that uniquely identify an entity, enabling services to resolve references to entities managed by other services.
Setting Up GraphQL Federation
Step 1: Define Federated Services
Each federated service should define its own schema and expose it using a GraphQL server. Here's an example of a simple federated service:
User Service Schema (user-service/schema.graphql)
type User @key(fields: "id") { id: ID! name: String! email: String! } type Query { user(id: ID!): User }
User Service Resolver (user-service/resolvers.js)
const { ApolloServer, gql } = require('apollo-server'); const { buildFederatedSchema } = require('@apollo/federation'); const typeDefs = gql` type User @key(fields: "id") { id: ID! name: String! email: String! } type Query { user(id: ID!): User } `; const resolvers = { Query: { user: (_, { id }) => { // Fetch user by ID from the database return { id, name: "John Doe", email: "[email protected]" }; }, }, User: { __resolveReference(user) { // Resolve user reference return { id: user.id, name: "John Doe", email: "[email protected]" }; }, }, }; const server = new ApolloServer({ schema: buildFederatedSchema([{ typeDefs, resolvers }]), }); server.listen({ port: 4001 }).then(({ url }) => { console.log(`User service ready at ${url}`); });
Step 2: Set Up the Gateway
The gateway aggregates the schemas from all federated services and provides a unified API.
Gateway Setup (gateway/index.js)
const { ApolloServer } = require('apollo-server'); const { ApolloGateway } = require('@apollo/gateway'); const gateway = new ApolloGateway({ serviceList: [ { name: 'user', url: 'http://localhost:4001' }, // Add other federated services here ], }); const server = new ApolloServer({ gateway, subscriptions: false, }); server.listen({ port: 4000 }).then(({ url }) => { console.log(`Gateway ready at ${url}`); });
Step 3: Extend Types Across Services
Federated services can extend types defined in other services. For example, a Post
service can extend the User
type to include posts authored by the user.
Post Service Schema (post-service/schema.graphql)
extend type User @key(fields: "id") { id: ID! @external posts: [Post] } type Post { id: ID! title: String! content: String! authorId: ID! } type Query { postsByUser(userId: ID!): [Post] }
Post Service Resolver (post-service/resolvers.js)
const { ApolloServer, gql } = require('apollo-server'); const { buildFederatedSchema } = require('@apollo/federation'); const typeDefs = gql` extend type User @key(fields: "id") { id: ID! @external posts: [Post] } type Post { id: ID! title: String! content: String! authorId: ID! } type Query { postsByUser(userId: ID!): [Post] } `; const resolvers = { User: { posts(user) { // Fetch posts by user ID from the database return [ { id: "1", title: "Post 1", content: "Content 1", authorId: user.id }, { id: "2", title: "Post 2", content: "Content 2", authorId: user.id }, ]; }, }, Query: { postsByUser: (_, { userId }) => { // Fetch posts by user ID from the database return [ { id: "1", title: "Post 1", content: "Content 1", authorId: userId }, { id: "2", title: "Post 2", content: "Content 2", authorId: userId }, ]; }, }, }; const server = new ApolloServer({ schema: buildFederatedSchema([{ typeDefs, resolvers }]), }); server.listen({ port: 4002 }).then(({ url }) => { console.log(`Post service ready at ${url}`); });
Step 4: Query the Unified API
Clients can now query the unified API provided by the gateway, which will delegate the queries to the appropriate federated services.
Example Query
Practical Exercise
Exercise: Implement a Federated Service
-
Create a Product Service
- Define a
Product
type with fieldsid
,name
, andprice
. - Implement a query to fetch a product by ID.
- Define a
-
Extend the User Type
- Extend the
User
type in the Product service to include a list of products purchased by the user.
- Extend the
-
Set Up the Gateway
- Add the Product service to the gateway's service list.
-
Query the Unified API
- Write a query to fetch a user along with the products they have purchased.
Solution
Product Service Schema (product-service/schema.graphql)
extend type User @key(fields: "id") { id: ID! @external purchasedProducts: [Product] } type Product { id: ID! name: String! price: Float! } type Query { product(id: ID!): Product }
Product Service Resolver (product-service/resolvers.js)
const { ApolloServer, gql } = require('apollo-server'); const { buildFederatedSchema } = require('@apollo/federation'); const typeDefs = gql` extend type User @key(fields: "id") { id: ID! @external purchasedProducts: [Product] } type Product { id: ID! name: String! price: Float! } type Query { product(id: ID!): Product } `; const resolvers = { User: { purchasedProducts(user) { // Fetch products purchased by user ID from the database return [ { id: "1", name: "Product 1", price: 100.0 }, { id: "2", name: "Product 2", price: 200.0 }, ]; }, }, Query: { product: (_, { id }) => { // Fetch product by ID from the database return { id, name: "Product 1", price: 100.0 }; }, }, }; const server = new ApolloServer({ schema: buildFederatedSchema([{ typeDefs, resolvers }]), }); server.listen({ port: 4003 }).then(({ url }) => { console.log(`Product service ready at ${url}`); });
Gateway Setup (gateway/index.js)
const { ApolloServer } = require('apollo-server'); const { ApolloGateway } = require('@apollo/gateway'); const gateway = new ApolloGateway({ serviceList: [ { name: 'user', url: 'http://localhost:4001' }, { name: 'post', url: 'http://localhost:4002' }, { name: 'product', url: 'http://localhost:4003' }, ], }); const server = new ApolloServer({ gateway, subscriptions: false, }); server.listen({ port: 4000 }).then(({ url }) => { console.log(`Gateway ready at ${url}`); });
Example Query
Conclusion
GraphQL Federation is a robust solution for scaling GraphQL across large organizations and microservices architectures. By composing multiple GraphQL services into a single, unified API, you can achieve a modular and maintainable schema design. This module covered the key concepts of federation, setting up federated services, extending types across services, and querying the unified API. With practical examples and exercises, you should now be equipped to implement GraphQL Federation in your own projects.