GraphQL has revolutionized API design by providing a flexible query language. For large-scale systems where multiple services expose their GraphQL APIs, combining these schemas efficiently becomes crucial. This chapter delves into two advanced techniques: GraphQL Federation and Schema Stitching, both of which allow developers to create unified schemas in Express.js applications.
GraphQL Federation is an architecture introduced by Apollo Federation to enable multiple GraphQL services to work together as a single unified API. It breaks down monolithic schemas into smaller, manageable services.
Schema Stitching is another method to combine multiple GraphQL schemas into one, allowing a single query endpoint for clients. Unlike Federation, Schema Stitching doesn’t require custom resolvers in sub-services but relies on combining schemas directly.
Feature | GraphQL Federation | Schema Stitching |
---|---|---|
Architecture | Service-oriented | Centralized |
Dependencies | Requires Apollo Federation libraries | Uses tools like @graphql-tools/stitch |
Resolvers | Defined in each service | Centralized in the stitching service |
Best Use Case | Large microservices architectures | Smaller-scale or legacy schemas |
To implement GraphQL Federation, you’ll need Apollo Federation libraries:
npm install apollo-server-express @apollo/subgraph graphql
A subgraph represents an individual service exposing a GraphQL schema.
users
Subgraph
const { ApolloServer } = require('apollo-server-express');
const { buildSubgraphSchema } = require('@apollo/subgraph');
const express = require('express');
const { gql } = require('graphql-tag');
const typeDefs = gql`
type User {
id: ID!
name: String!
}
type Query {
users: [User!]!
}
`;
const resolvers = {
Query: {
users: () => [
{ id: '1', name: 'Alice' },
{ id: '2', name: 'Bob' },
],
},
};
const app = express();
const server = new ApolloServer({
schema: buildSubgraphSchema({ typeDefs, resolvers }),
});
server.start().then(() => {
server.applyMiddleware({ app });
app.listen(4001, () => console.log('Users service running on http://localhost:4001'));
});
The Gateway combines multiple subgraphs.
npm install @apollo/gateway apollo-server-express graphql
const { ApolloServer } = require('apollo-server-express');
const { ApolloGateway } = require('@apollo/gateway');
const express = require('express');
const gateway = new ApolloGateway({
serviceList: [
{ name: 'users', url: 'http://localhost:4001/graphql' },
],
});
const app = express();
const server = new ApolloServer({
gateway,
subscriptions: false,
});
server.start().then(() => {
server.applyMiddleware({ app });
app.listen(4000, () => console.log('Gateway running on http://localhost:4000'));
});
Suppose there’s another subgraph, posts
, and it extends the User
type.
posts
Subgraph
const { ApolloServer } = require('apollo-server-express');
const { buildSubgraphSchema } = require('@apollo/subgraph');
const express = require('express');
const { gql } = require('graphql-tag');
const typeDefs = gql`
extend type User @key(fields: "id") {
id: ID! @external
posts: [Post!]!
}
type Post {
id: ID!
title: String!
authorId: ID!
}
type Query {
posts: [Post!]!
}
`;
const resolvers = {
User: {
posts: (user) => [
{ id: '101', title: 'Post 1', authorId: user.id },
{ id: '102', title: 'Post 2', authorId: user.id },
],
},
Query: {
posts: () => [
{ id: '101', title: 'Post 1', authorId: '1' },
{ id: '102', title: 'Post 2', authorId: '2' },
],
},
};
const app = express();
const server = new ApolloServer({
schema: buildSubgraphSchema({ typeDefs, resolvers }),
});
server.start().then(() => {
server.applyMiddleware({ app });
app.listen(4002, () => console.log('Posts service running on http://localhost:4002'));
});
To implement schema stitching, you’ll need @graphql-tools
:
npm install @graphql-tools/schema @graphql-tools/stitch graphql
Let’s stitch the users
and posts
schemas.
const { stitchSchemas } = require('@graphql-tools/stitch');
const { makeExecutableSchema } = require('@graphql-tools/schema');
const express = require('express');
const { graphqlHTTP } = require('express-graphql');
// User schema
const userTypeDefs = `
type User {
id: ID!
name: String!
}
type Query {
users: [User!]!
}
`;
const userResolvers = {
Query: {
users: () => [
{ id: '1', name: 'Alice' },
{ id: '2', name: 'Bob' },
],
},
};
// Post schema
const postTypeDefs = `
type Post {
id: ID!
title: String!
authorId: ID!
}
type Query {
posts: [Post!]!
}
`;
const postResolvers = {
Query: {
posts: () => [
{ id: '101', title: 'Post 1', authorId: '1' },
{ id: '102', title: 'Post 2', authorId: '2' },
],
},
};
// Combine schemas
const stitchedSchema = stitchSchemas({
subschemas: [
makeExecutableSchema({ typeDefs: userTypeDefs, resolvers: userResolvers }),
makeExecutableSchema({ typeDefs: postTypeDefs, resolvers: postResolvers }),
],
});
// Create Express app
const app = express();
app.use('/graphql', graphqlHTTP({ schema: stitchedSchema, graphiql: true }));
app.listen(4003, () => console.log('Stitched API running on http://localhost:4003/graphql'));
Feature | Federation | Schema Stitching |
---|---|---|
Decentralization | Distributed schema with separate services | Centralized schema combination |
Complexity | Higher (requires resolvers for extensions) | Moderate (combines schemas directly) |
Tooling | Apollo Federation-specific tools | General GraphQL tooling |
@key
: Defines unique identifiers in a subgraph.@external
: Indicates fields managed by other subgraphs.@requires
: Fetches dependent fields for computation.DataLoader
.GraphQL Federation and Schema Stitching provide powerful ways to manage and integrate GraphQL APIs in Express.js. Federation is ideal for large-scale, service-oriented systems, while Schema Stitching offers a simpler, centralized approach. By understanding these concepts and applying the techniques demonstrated in this chapter, developers can build flexible, scalable APIs tailored to their application architecture. Happy coding !❤️