One schema is the whole point for clients, but one server owned by one team does not scale to an organisation. Federation and stitching let several services publish parts of one graph behind one endpoint. After this lesson you will be able to write a federated subgraph with entities, compose subgraphs behind a router, and know when stitching is enough.
| Approach | Where the graph is combined | Who declares relationships | Best for |
|---|---|---|---|
| Apollo Federation | A router serving a composed supergraph | Each subgraph, via @key | Many teams owning domains |
| Schema stitching | A gateway using @graphql-tools/stitch | The gateway, by merging types | Existing or third-party APIs |
Each service still runs an ordinary GraphQL server; the difference is where knowledge of how types connect lives.
Federation revolves around entities, types that several subgraphs contribute fields to. A subgraph declares one with @key and provides __resolveReference so the router can fetch it by key:
import { ApolloServer } from "@apollo/server";
import { buildSubgraphSchema } from "@apollo/subgraph";
import gql from "graphql-tag";
const typeDefs = gql`
extend schema @link(url: "https://specs.apollo.dev/federation/v2.9", import: ["@key"])
type User @key(fields: "id") { id: ID! name: String! }
type Query { user(id: ID!): User }
`;
const resolvers = {
Query: { user: (_p, { id }, { db }) => db.users.find(id) },
User: { __resolveReference: (ref, { db }) => db.users.find(ref.id) },
};
const server = new ApolloServer({ schema: buildSubgraphSchema({ typeDefs, resolvers }) });A second subgraph can add fields to User without owning its data; it needs only the key:
const typeDefs = gql`
# same @link header as above
type Review @key(fields: "id") { id: ID! body: String! author: User! }
type User @key(fields: "id") { id: ID! reviews: [Review!]! }
`;
const resolvers = {
Review: { author: (review) => ({ __typename: "User", id: review.authorId }) },
User: { reviews: (user, _a, { db }) => db.reviews.byAuthor(user.id) },
};Returning { __typename: "User", id } is enough: the router knows name lives in the users subgraph and calls its __resolveReference. A query for { user(id: "1") { name reviews { body } } } is planned across both services.
Subgraphs are composed into a supergraph schema that a router executes. In development, @apollo/gateway composes at startup by introspecting the subgraphs:
import { ApolloGateway, IntrospectAndCompose } from "@apollo/gateway";
const gateway = new ApolloGateway({
supergraphSdl: new IntrospectAndCompose({
subgraphs: [
{ name: "users", url: "http://localhost:4001/" },
{ name: "reviews", url: "http://localhost:4002/" },
],
}),
});For production, compose ahead of time with rover supergraph compose --config supergraph.yaml > supergraph.graphql and run Apollo Router, a Rust binary with query planning, caching and telemetry. Composition fails when subgraphs conflict, turning integration problems into build errors.
Stitching combines complete schemas in a gateway. Each remote schema gets an executor, and the gateway merges types with the same name:
import { stitchSchemas } from "@graphql-tools/stitch";
import { buildHTTPExecutor } from "@graphql-tools/executor-http";
import { schemaFromExecutor } from "@graphql-tools/wrap";
const usersExec = buildHTTPExecutor({ endpoint: "http://localhost:4001/graphql" });
const reviewsExec = buildHTTPExecutor({ endpoint: "http://localhost:4002/graphql" });
const schema = stitchSchemas({
subschemas: [
{ schema: await schemaFromExecutor(usersExec), executor: usersExec },
{ schema: await schemaFromExecutor(reviewsExec), executor: reviewsExec },
],
});A merge configuration per subschema plays the role of @key. Stitching needs no changes to the underlying services.
id), and run composition checks in CI.What is the purpose of `__resolveReference` in a subgraph?
@key and implements __resolveReference; others extend them via stubs.@apollo/gateway.@graphql-tools/stitch merges complete remote schemas, ideal for legacy APIs.Next lesson: GraphQL Best Practices and When to Use It — consolidate naming, nullability and evolution rules, and weigh GraphQL against REST.