Back to Latest PostsBlog

GraphQL and Apollo in Production: Lessons from a Microservices Platform

By Carlos Diaz · August 14, 2024 · 3 min read

We run GraphQL and REST side by side in production, and the choice of which to use for a given endpoint is not ideological - it comes down to whether the consuming client's data needs are homogeneous (REST wins, it is simpler) or genuinely varied across clients (GraphQL earns its complexity).

Schema-first design is the discipline that makes GraphQL worth the investment. Writing the schema before the resolvers turns it into an actual contract that frontend and backend teams can build against in parallel, instead of the frontend waiting on backend implementation to know what shape of data it will receive. Apollo Client's normalized cache is the other half of the value proposition on the frontend: once an entity is in the cache, navigating to a view that needs the same entity is often instant, with no network request at all, which is a class of performance win that is very hard to replicate with a REST client without building the same caching layer yourself.

The operational lessons are less glamorous but matter more once you are at scale. The N+1 query problem is not theoretical - a naively written resolver that fetches a related entity per item in a list will silently degrade a query from one database round trip to hundreds under real data volumes, and dataloaders solve this by batching and deduplicating those fetches within a single request. Query depth and complexity limits are not optional either; without them, a client - malicious or just badly written - can construct a deeply nested query that turns into an expensive database join explosion server-side.

Testing GraphQL APIs well means testing at the resolver level and at the schema level separately: resolver tests catch business logic bugs, while schema snapshot tests catch accidental breaking changes to the contract that frontend clients depend on. We run both in CI, and the schema check has caught more accidental breaking changes than I expected it to.

In a microservices setup, federation lets each service own its part of the graph without a single team owning a monolithic schema, while still presenting one coherent API to clients. The gateway becomes infrastructure the platform team owns, and the subgraphs become something each service team can evolve independently - which is the same independence goal that motivated microservices in the first place, just applied to the API layer.

Key Takeaways

  • Schema-first design turns the API into a real contract between frontend and backend teams.
  • Apollo Client's normalized cache makes navigation feel instant across the app.
  • Dataloaders prevent N+1 query problems before they reach production.
  • Query depth limits and resolver-level monitoring protect the platform from day one.
  • Federation lets each service own its part of the graph while clients see one coherent API.