Skip to main content
Blog

June 5, 2026

GraphQL Federation at scale — what I learned shipping it to a US telecom

Photo of Fabio Borges

Fabio Borges

GraphQL Federation sounds like an infrastructure problem. It isn't. It's an organizational problem — how do you let teams own their domain's schema without creating a distributed monolith that collapses under the weight of cross-team coordination?

The setup I worked on at BairesDev processes 1,000+ daily transactions for a US telecom's AI-powered checkout flow. Multiple subgraphs, each owned by a different team, unified behind a single gateway. Here's what actually shipping it taught me.

We used Apollo Federation v2 with WunderGraph as the composition and gateway layer. The basic topology:

Client → WunderGraph Gateway → [catalog subgraph, pricing subgraph, checkout subgraph, identity subgraph]

WunderGraph handles:

  • Schema composition from all subgraphs (with breaking-change detection in CI)
  • Type-safe client generation
  • Request routing and query planning
  • Response caching for eligible queries

Each subgraph owns its SDL as a contract. No subgraph knows about another subgraph's resolvers — only about the federated entities it shares via @key directives.

In a monolithic GraphQL server, N+1 is straightforward: you're querying a list, then resolving a field on each item separately. DataLoader fixes it.

In a federated setup, N+1 manifests differently. The gateway sends a batch request to a subgraph using @requires — asking for entity data in one shot. But if your reference resolver isn't batching correctly, you get N round-trips from the gateway to the subgraph instead of one.

The fix is the same (DataLoader, batch loaders), but the debugging path is different. You can't just add a console log to the resolver — you need to look at the query plan the gateway produces and trace the subgraph calls in your observability layer.

We chose WunderGraph over Apollo Router primarily for type-safe client generation. The generated client knows the exact shape of every operation, which eliminates an entire class of runtime type errors on the frontend. When a subgraph changes a type, the client regenerates and TypeScript catches the incompatibility at build time.

The tradeoff: WunderGraph is less battle-tested at extreme scale than Apollo Router. For our 1,000+ daily transaction volume, it's been solid. At 100,000+ daily transactions I'd evaluate more carefully.

Federated graphs are hard to debug without good observability because a single client query becomes multiple subgraph queries. A slow response could be slow composition, a slow subgraph, a N+1 reference resolver, or a caching miss.

We instrumented with New Relic flow-tracking at the resolver level. For each subgraph call, we track:

  • Time to first byte from the gateway's perspective
  • Reference resolver batch size (to catch N+1 before it becomes a problem in prod)
  • Schema version (so we can correlate regressions with deploys)

The key metric we watch is reference resolver batch efficiency: the ratio of resolved entities to batched requests. A healthy ratio is close to 1:1 (one batch = all entities). When it starts creeping toward N:1, something is wrong with the DataLoader setup.

Schema-first means the SDL is a contract. Breaking changes — removing a field, changing a type, making a nullable field non-null — are deployments that break clients. We run schema change detection in CI on every PR touching a subgraph:

  1. The PR's schema is composed against all other subgraphs
  2. The resulting supergraph is diffed against the current production supergraph
  3. Breaking changes fail the CI check with a clear error message

This prevents "I only changed my subgraph" accidents. The federation is a shared contract.

Start with a simpler gateway setup. We over-engineered the initial WunderGraph configuration trying to optimize for features we didn't need on day one. A simpler composition-only setup would have shipped faster.

Write entity resolver tests first. Reference resolvers are where federation bugs hide. A unit test that verifies your entity resolver receives a correctly batched set of __typename + id pairs catches most issues before they reach integration testing.

Log query plans in staging. The gateway's query plan — which subgraphs get called, in what order, with what arguments — is the ground truth for debugging performance. Log it in staging from day one.

The code lives inside the BairesDev codebase so I can't open-source it, but the architecture is standard Apollo Federation v2. If you're building something similar, the Apollo Federation docs and the WunderGraph docs between them cover 80% of what you need to know. The remaining 20% is the operational stuff above.

Share this post:

Discuss this post on Dev.to
23 GitHub repos
AI Assistant
Fabio's AI assistant

Hi, I'm Fabio's AI assistant!

Ask me about his experience, skills, projects, or anything related to his portfolio.

0/500