Backend Development7 min read

GraphQL Advanced Patterns

Advanced GraphQL: federation with Apollo, subscriptions via WebSocket, DataLoader for N+1 prevention, persisted queries, and schema design patterns.

graphqlapifederationsubscriptions

Advanced GraphQL tackles the hard problems: N+1 query performance, schema federation across microservices, real-time subscriptions, and schema evolution. These patterns appear when GraphQL APIs serve production-scale workloads.

Use DataLoader for every resolver that loads related data - N+1 is the most common GraphQL performance problem. Use Federation when a microservices team owns different graph sections. Design schemas around business capabilities, not database tables.

Workflow

  1. Eliminate N+1 with DataLoaderWithout DataLoader, GraphQL naively issues one query per item in a list - e.g., 100 user queries for a 100-post list.
  2. Implement Apollo FederationFederation lets multiple services own parts of the graph while clients see one unified schema.
  3. Add real-time subscriptionsSubscriptions push data to clients on events - chat messages, live feeds, notifications.
  4. Use persisted queries for productionPersisted queries reduce payload size, prevent arbitrary query execution, and enable CDN caching of queries.
  5. Design schemas for evolutionGraphQL schemas must evolve without breaking clients - proper design prevents painful migrations.

Output Format

DataLoader per resolver type, Federation subgraph annotations, WebSocket subscriptions, APQ enabled, `@deprecated` used for schema evolution, breaking change CI check.

Examples

DataLoader: `const productLoader = new DataLoader<string, Product>(async (ids) => { const products = await db.product.findMany({ where: { id: { in: [...ids] } } }); const map = new Map(products.map(p => [p.id, p])); return ids.map(id => map.get(id) ?? new Error(`Product ${id} not found`)); })`
Federation entity: `const resolvers = { Product: { __resolveReference: (ref, { dataSources }) => dataSources.productsAPI.getProduct(ref.id) } }`
Subscription: `subscribe: (_root, { roomId }, { pubsub }) => pubsub.asyncIterableIterator([`CHAT_${roomId}`])`

Edge Cases & Gotchas

  • DataLoader key ordering: the batch function MUST return results in the same order as input keys; use a Map for O(1) lookup when filling the return array
  • Federation @external fields: fields annotated @external must be available from the owning subgraph via __resolveReference - ensure the owning service implements it correctly
  • Subscription scaling: subscriptions are stateful (WebSocket connections) and don't work with serverless; use Redis PubSub or a dedicated message broker for horizontal scaling

When to Break These Rules

  • For simple read-heavy APIs with 1-2 clients, REST is simpler and performs equally well. GraphQL's complexity is justified when multiple clients need different data shapes from the same API.

Reference Files

  • Apollo Federation: https://www.apollographql.com/docs/federation/
  • DataLoader: https://github.com/graphql/dataloader
  • graphql-ws: https://github.com/enisdenjo/graphql-ws
  • graphql-inspector: https://the-guild.dev/graphql/inspector

More skills in this library

Browse all technical skills - Mobile, AI/ML, Web, Backend, Design, and Product.