GraphQL Advanced Patterns
Advanced GraphQL: federation with Apollo, subscriptions via WebSocket, DataLoader for N+1 prevention, persisted queries, and schema design patterns.
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
- 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.
- Implement Apollo FederationFederation lets multiple services own parts of the graph while clients see one unified schema.
- Add real-time subscriptionsSubscriptions push data to clients on events - chat messages, live feeds, notifications.
- Use persisted queries for productionPersisted queries reduce payload size, prevent arbitrary query execution, and enable CDN caching of queries.
- 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/dataloadergraphql-ws: https://github.com/enisdenjo/graphql-wsgraphql-inspector: https://the-guild.dev/graphql/inspector