Advanced GraphQL Federation Patterns

GraphQL federation distributes schema ownership across independent CMS subgraphs behind one gateway, so each content service deploys on its own cycle while clients query a single graph. The tradeoff you take on: strict boundary enforcement, explicit entity keys, and composition validation in CI. This is the pattern that scales headless content delivery without a monolithic content graph — relevant whenever you weigh Headless CMS Architecture & Platform Selection for enterprise builds.

Gateway composition

The composition router is the single entry point for clients. Apollo Router v1.30+ (or an open-source equivalent) handles composition validation, query planning, and execution routing. Define the supergraph explicitly so type collisions surface at compose time instead of as runtime schema drift.

At a glance, the router fronts independently owned subgraphs and resolves cross-service joins in one execution plan:

flowchart TD
  Client["Frontend client"] --> Router["Composition router (supergraph)"]
  Router -->|"query plan"| Commerce["commerce subgraph"]
  Router -->|"query plan"| Editorial["editorial subgraph"]
  Commerce -->|"@key: id"| Join["Entity resolution + DataLoader batch"]
  Editorial -->|"@key: productId"| Join
  Join --> Router
  Router -->|"merged + @cacheControl"| Client
YAML
# supergraph.yaml
federation_version: 2
subgraphs:
  commerce:
    routing_url: https://commerce-cms.internal/graphql
    schema:
      file: ./subgraphs/commerce.graphql
  editorial:
    routing_url: https://editorial-cms.internal/graphql
    schema:
      file: ./subgraphs/editorial.graphql

rover supergraph compose --config supergraph.yaml generates the executable schema; validate type collisions here, before deploy, to avoid breaking client contracts. The GraphQL vs REST API Tradeoffs explain why federation curbs over- and under-fetching across multi-source content when the frontend needs precise payload control.

Entity resolution and cross-service joins

Cross-service joins use @key directives and __resolveReference implementations. Each subgraph declares primary identifiers for shared entities. Avoid implicit joins — explicit keys prevent N+1 cascades and keep resolver execution predictable.

GraphQL
# commerce.graphql
type Product @key(fields: "id") {
  id: ID!
  sku: String!
  price: Money
  editorialContent: ProductEditorial @external
}

type ProductEditorial @key(fields: "productId") @extends {
  productId: ID! @external
  seoTitle: String
  richText: JSON
}

The editorial subgraph resolves localized metadata on demand. Aligning field boundaries with Content Modeling Best Practices prevents circular dependencies and keeps domain ownership clear. For batch resolution, use DataLoader to aggregate requests across products within one execution cycle.

TypeScript
// editorial-resolver.ts
import DataLoader from 'dataloader';

// A single DataLoader instance is created per request and shared via context,
// so calls across multiple products batch into one upstream fetch.
export function createEditorialLoader(cms) {
  return new DataLoader(async (ids: string[]) => {
    const results = await cms.fetchByProductIds(ids);
    return ids.map(id => results.find(r => r.productId === id) || null);
  });
}

export const resolvers = {
  ProductEditorial: {
    __resolveReference: (reference, context) => {
      return context.editorialLoader.load(reference.productId);
    }
  }
};

Caching federated content

Gateway caching needs precise @cacheControl directives and HTTP header propagation. Set max-age per subgraph to match content volatility — editorial content takes shorter TTLs than commerce catalogs or static assets.

GraphQL
extend type Query {
  productFeed(locale: String!): [Product!]! @cacheControl(maxAge: 3600, scope: PUBLIC)
}

Configure the router to respect upstream Cache-Control headers and propagate ETag/Last-Modified to edge CDNs. Apply cache tags at the entity level for granular invalidation without full purges — detail in Federating multiple headless CMS sources with GraphQL.

Federation vs. stitching

Federation is the default for distributed schemas. Some legacy integrations still use schema stitching, but as Schema stitching for multi-vendor headless architectures shows, stitching lacks native type ownership and query-planning optimization, so it strains at enterprise scale. Federation’s standardized directives and tooling are documented in the Apollo Federation specification.

For Cross-service data aggregation with Apollo Federation, enforce CI schema checks, trace with OpenTelemetry, and set SLAs for subgraph latency. Following the GraphQL specification keeps type resolution and error handling consistent across federated boundaries.

Conclusion

Federation works when boundaries are strict: explicit subgraph ownership, DataLoader-batched entity resolution, and caching tied to content lifecycle. Get those right and frontend teams consume one unified graph without coupling to any backend’s deploy cycle.