GraphQL Federated Entity Pattern
Share entity types across federated GraphQL services so the gateway can resolve fields from multiple subgraphs transparently.
Note: This guide follows English-language naming conventions and terminology standards common in international development teams. Examples use English identifiers and comments to maximize compatibility across codebases and tooling.
GraphQL Federated Entity Pattern
Overview
In Apollo Federation, an entity is an object type shared across multiple subgraphs. Each subgraph contributes different fields to the same entity. The gateway stitches them together so clients query a single unified type without knowing which service owns which field.
Entities are the building blocks of a federated graph. They use the @key directive to declare a primary key, @extends to add fields from another subgraph, and @external to reference fields owned by other services. The gateway resolves entities by calling each subgraph’s _entities field with the appropriate keys.
When to Use
- Multiple services own different fields of the same domain entity (e.g. User has profile in one service, orders in another)
- You are building a microservices architecture with GraphQL
- You need a unified API gateway without coupling services
- Schema stitching is insufficient because you need type merging at the entity level
- You want to split a monolithic GraphQL schema into domain-owned subgraphs
Solution
Subgraph A: User Service (owns User entity)
import { buildSubgraphSchema } from '@apollo/subgraph';
import { ApolloServer } from '@apollo/server';
const typeDefs = `
type User @key(fields: "id") {
id: ID!
name: String!
email: String!
profile: Profile
}
type Profile {
bio: String
avatarUrl: String
}
type Query {
user(id: ID!): User
users: [User!]!
}
`;
const resolvers = {
User: {
__resolveReference: (user, context) => {
// Called by the gateway when another subgraph references this entity
return context.dataSources.userApi.getById(user.id);
},
},
Query: {
user: (_, { id }, context) => context.dataSources.userApi.getById(id),
users: (_, __, context) => context.dataSources.userApi.getAll(),
},
};
const server = new ApolloServer({
schema: buildSubgraphSchema([{ typeDefs, resolvers }]),
});
Subgraph B: Order Service (extends User entity)
const typeDefs = `
type User @key(fields: "id") @extends {
id: ID! @external
orders: [Order!]!
}
type Order @key(fields: "id") {
id: ID!
userId: ID!
total: Float!
items: [OrderItem!]!
}
type OrderItem {
productId: ID!
quantity: Int!
price: Float!
}
type Query {
order(id: ID!): Order
ordersByUser(userId: ID!): [Order!]!
}
`;
const resolvers = {
User: {
orders: (user, _, context) => {
// Gateway provides user.id from the parent subgraph
return context.dataSources.orderApi.getByUserId(user.id);
},
},
Query: {
order: (_, { id }, context) => context.dataSources.orderApi.getById(id),
ordersByUser: (_, { userId }, context) => context.dataSources.orderApi.getByUserId(userId),
},
};
const server = new ApolloServer({
schema: buildSubgraphSchema([{ typeDefs, resolvers }]),
});
Subgraph C: Review Service (extends both User and Product)
const typeDefs = `
type User @key(fields: "id") @extends {
id: ID! @external
reviews: [Review!]!
}
type Product @key(fields: "id") @extends {
id: ID! @external
reviews: [Review!]!
}
type Review @key(fields: "id") {
id: ID!
userId: ID!
productId: ID!
rating: Int!
comment: String
}
type Query {
reviewsByProduct(productId: ID!): [Review!]!
}
`;
const resolvers = {
User: {
reviews: (user, _, context) =>
context.dataSources.reviewApi.getByUserId(user.id),
},
Product: {
reviews: (product, _, context) =>
context.dataSources.reviewApi.getByProductId(product.id),
},
};
Gateway Setup
import { ApolloGateway } from '@apollo/gateway';
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';
const gateway = new ApolloGateway({
serviceList: [
{ name: 'users', url: 'http://localhost:4001/graphql' },
{ name: 'orders', url: 'http://localhost:4002/graphql' },
{ name: 'reviews', url: 'http://localhost:4003/graphql' },
],
});
const server = new ApolloServer({ gateway });
startStandaloneServer(server, { listen: { port: 4000 } });
Client Query (Cross-Service)
query GetUserWithOrdersAndReviews {
user(id: "123") {
id
name
email
orders {
id
total
items {
productId
quantity
}
}
reviews {
id
rating
comment
}
}
}
Explanation
Federation works through three directives:
@key(fields: "id")— declares the primary key of an entity. The gateway uses this key to request the entity from any subgraph that owns it.@extends— marks a type as an extension. The subgraph does not own the type but adds fields to it.@external— marks a field as owned by another subgraph. The resolver receives this field’s value from the gateway.
When a client queries user(id: "123") { orders { total } }, the gateway:
- Sends the query to the users subgraph to resolve
user(id: "123")and fetchid,name,email - Sends the
idto the orders subgraph via_entitiesto resolveUser.orders - Merges the results and returns a single
Userobject to the client
The __resolveReference resolver is called when the gateway requests an entity by its key. Each subgraph that owns the entity must implement this resolver to fetch the base fields.
Variants
| Approach | Directives | Best For |
|---|---|---|
| Single key | @key(fields: "id") | Standard entities with one ID |
| Composite key | @key(fields: "orgId id") | Multi-tenant or partitioned entities |
| Multiple keys | @key(fields: "id") @key(fields: "email") | Lookup by different identifiers |
| Entity with computed fields | @requires | Fields computed from other subgraph fields |
| Shareable fields | @shareable | Fields resolved by multiple subgraphs |
Best Practices
-
For a deeper guide, see GraphQL Connection Pagination Pattern.
-
One owning service per entity — only one subgraph should own the base type definition. Others use
@extends. -
Use
__resolveReferenceefficiently — the gateway calls it for every entity reference. Batch database calls when multiple entities are requested. -
Keep entities small — only declare fields that this subgraph owns. Extending with too many fields creates a heavy gateway query plan.
-
Use
@requiresfor computed fields — if a field needs data from another subgraph, declare it with@requiresso the gateway fetches the dependency first. -
Version entities carefully — changing
@keyfields breaks the gateway. Add new keys before removing old ones.
Common Mistakes
- Missing
__resolveReference— without it, the gateway cannot resolve entity references from other subgraphs. The query fails with a runtime error. - Declaring
@extendswithout@external— extended types must mark inherited fields as@external. Forgetting this causes schema validation errors. - Multiple subgraphs owning the same entity — only one subgraph should define the base type. Others must use
@extends. - Circular entity references — Subgraph A extends User with orders, Subgraph B extends Order with user. The gateway handles this, but deep circular queries create expensive query plans.
- Not testing the query plan — use Apollo Studio’s query plan viewer to verify the gateway fetches fields from the right subgraphs in the right order.
Frequently Asked Questions
How does federation differ from schema stitching?
Federation is a specification: subgraphs implement @key, @extends, and _entities. The gateway uses these to build a query plan. Schema stitching manually merges schemas with custom delegation logic. Federation is more structured; stitching is more flexible.
Can a subgraph extend an entity it does not own?
Yes. That is the core of federation. The subgraph uses @extends and @external to add fields. The gateway routes field requests to the owning subgraph for base fields and to the extending subgraph for the new fields.
What is @requires for?
@requires lets a subgraph compute a field using data from another subgraph. For example, the shipping subgraph can define User.shippingCost with @requires(fields: "address") where address is owned by the users subgraph. The gateway fetches address first, then passes it to the shipping subgraph.
Can I use federation with REST services?
Not directly. Subgraphs must be GraphQL services that implement the federation spec. To integrate REST, create a GraphQL wrapper that calls the REST API and expose it as a subgraph.
Advanced Topics
Scenario: Federation for GraphQL Microservices
# Subgraph A: Users service
type User @key(fields: "id") {
id: ID!
name: String!
email: String!
# @external: resolved by another subgraph
orders: [Order!] @requires(fields: "id")
}
# Subgraph B: Orders service
type Order @key(fields: "id") {
id: ID!
userId: ID!
total: Float!
# @provides: this subgraph resolves the field
user: User @provides(fields: "name")
}
# Query: the gateway merges both subgraphs
query {
user(id: "123") {
name # resolved by Users service
orders { # resolved by Orders service
id
total
}
}
}
// Apollo Federation: resolve entity reference
const resolvers = {
User: {
// __resolveReference: called when another subgraph references User
__resolveReference(user, ctx) {
return ctx.dataSources.userAPI.getUserById(user.id);
},
orders(user, _, ctx) {
return ctx.dataSources.orderAPI.getOrdersByUserId(user.id);
},
},
Order: {
__resolveReference(order, ctx) {
return ctx.dataSources.orderAPI.getOrderById(order.id);
},
user(order, _, ctx) {
return { __typename: "User", id: order.userId };
},
},
};
// Gateway: merges subgraphs
const gateway = new ApolloGateway({
serviceList: [
{ name: "users", url: "http://users-service:4001/graphql" },
{ name: "orders", url: "http://orders-service:4002/graphql" },
],
});
const server = new ApolloServer({ gateway });
Lessons:
- Federation: multiple subgraphs expose a unified schema
- @key: defines the field that identifies the entity across subgraphs
- __resolveReference: resolves the entity when another subgraph references it
- The gateway routes the query to the corresponding subgraphs
- Each service is independent: deploy, scaling, separate team
- @extends: add fields to a type defined in another subgraph
### Federation vs Schema Stitching: which do I use?
Federation is the modern Apollo standard: each subgraph uses directives (@key, @provides, @requires) and the gateway resolves automatically. Schema Stitching is manual: the gateway defines resolvers that call each service. Federation is declarative: the gateway infers the execution plan. Stitching is imperative: you write the gateway resolvers. For new projects, Federation. For integrating existing non-Apollo APIs, Stitching. Related Resources
GraphQL Schema Stitching Pattern
Merge multiple independent GraphQL schemas into a single unified schema that clients can query as one graph.
PatternGraphQL Connection Pagination Pattern
Implement Relay-style cursor-based pagination with edges, nodes, and pageInfo for stable GraphQL list queries.
RecipeSet Up a GraphQL Federation Gateway with Apollo
Compose multiple GraphQL services into a single federated supergraph using Apollo Federation and a gateway that routes queries across subgraphs
PatternGraphQL Mutation Validation Pattern
Centralize input validation for GraphQL mutations using custom validators, schema directives, and structured error responses.
DocGraphQL Federation Onboarding Template
Template for onboarding a service to a federated GraphQL graph: subgraph setup, entity definitions, resolver configuration, gateway integration, testing, deployment, and monitoring with code examples.
GuideComplete Guide to GraphQL Federation
Build unified GraphQL APIs across multiple services with Apollo Federation. Covers subgraphs, supergraph composition, entity resolution, and gateway deployment.