In October 2022, I joined Wes Bos and Scott Tolinsky on the Syntax.fm Supper Club podcast to discuss how we used GraphQL as an aggregation layer at Sky TV. This post expands on the key architectural patterns and lessons from that conversation.
The Problem: Microservice Sprawl
Sky's streaming platforms (NOW TV, Sky Go, Peacock TV, Sky Showtime) served millions of daily users across 12 European territories. Each frontend app was making dozens of REST calls to different microservices: content catalogues, user profiles, entitlements, recommendations, analytics, and more.
This created several problems:
- Frontend bloat: every app duplicated data-fetching and transformation logic
- Slow page loads: multiple sequential API calls before anything could render
- Inconsistency: different apps interpreted the same API data differently
- Fragile deployments: backend API changes could break any of the 5+ frontend apps
The Solution: GraphQL as a Backend for Frontends
We built a GraphQL BFF (Backend for Frontends) that sat between the frontend apps and the microservices. Instead of each app calling 12 different services, they made a single GraphQL query that returned exactly the data they needed.
Key Architecture Decisions
1. Schema-first design:We defined the GraphQL schema based on what frontends needed, not on what backends provided. This decoupled frontend development from backend API changes.
2. DataLoader pattern for batching:Using DataLoader to batch and deduplicate requests to backend services. A single page render that previously made 30+ REST calls was reduced to 5-8 batched calls.
3. Federation-ready resolvers:Each resolver mapped to a specific backend service, making it straightforward to split into a federated architecture later.
4. Aggressive caching:We implemented a multi-layer cache: in-memory per-request deduplication, Redis for shared caching across instances, and CDN caching for public content queries.
Results
- 99.9% uptime across all frontend applications
- 50% reduction in API response times:from aggregation and caching
- 83% reduction in territory-specific codebases:12 apps consolidated to 2
- 30% faster feature development:frontends no longer blocked by backend API changes
Lessons Learned
GraphQL is not a silver bullet
GraphQL shines as an aggregation layer but it's not always the right choice. For simple CRUD APIs with a single consumer, REST is simpler and more predictable. GraphQL's value increases proportionally with the number of consumers and the complexity of data relationships.
Schema governance matters
With a 4-person team maintaining the BFF, we needed clear ownership rules. We established a rotating "boomerang" role where team members cycled through ownership of different schema domains. This prevented knowledge silos and ensured everyone understood the full system.
Performance monitoring is different
Traditional REST monitoring (response time per endpoint) doesn't work well for GraphQL where a single endpoint handles wildly different queries. We instrumented per-resolver timing and built dashboards showing the slowest resolvers and most expensive queries. This let us identify bottlenecks that would have been invisible with endpoint-level monitoring.
TypeScript makes it viable
Strong typing across the full stack, from GraphQL schema to TypeScript resolvers to TypeScript frontends, eliminated an entire class of bugs. Schema changes were caught at compile time rather than in production.
Listen to the Full Discussion
The podcast episode covers more ground including Sky's Apple TV app architecture, our CI/CD pipeline optimizations, and thoughts on the JavaScript ecosystem.
Listen on Syntax.fm: Episode 529:GraphQL as an Aggregation Layer