I've seen GraphQL touted as a silver bullet for all API problems, but the truth is more nuanced. It launched from Facebook as open source in 2015 and has since become a widely adopted technology.
GraphQL addresses a specific set of issues: over-fetching and under-fetching that plagues REST APIs. A single GraphQL query can precisely specify the needed data and fetch it from multiple resources in one request, making it particularly useful for client-heavy applications where data needs vary significantly.
However, GraphQL's most common production problem is the N+1 query, which can lead to a multitude of database queries. Facebook's DataLoader solution batched and deduplicated requests, but implementing it correctly requires significant effort, especially for resolvers with relational data. For instance, I've seen a single query result in over 100 database calls, resulting in a 10-second delay, which is unacceptable for most applications. To mitigate this, we used a combination of Redis and Memcached to cache frequently accessed data, reducing the delay to under 100 milliseconds.
In one particular case, we were dealing with a resolver that fetched data from a relational database, and the N+1 query problem was causing significant performance issues. We implemented a custom batching solution using Apache Kafka, which allowed us to batch requests and reduce the number of database queries by over 90%. This not only improved performance but also reduced the load on our database, allowing us to handle a higher volume of requests.
For simple CRUD APIs with consistent data shapes and performance constraints, REST remains a simpler choice to build, test, and cache. HTTP caching works well with REST, whereas caching GraphQL queries requires application-level caching and careful consideration of cache invalidation for mutations. I've seen many cases where the added complexity of GraphQL caching is not justified by the benefits, and a well-designed REST API with proper caching can achieve similar performance at a lower cost.
In scenarios where APIs are consumed by third parties, REST's predictability and tool support make it a more practical option. Swagger and Postman are just a few examples of the tooling available for REST APIs. When dealing with third-party APIs, it's especially important to consider the trade-offs between the flexibility of GraphQL and the simplicity of REST. For example, if you're building a public API that will be consumed by a wide range of clients, REST may be a better choice due to its wider adoption and simpler caching model.
GraphQL federation, which composes multiple downstream GraphQL subgraphs into a unified API, addresses the scaling problem faced by monolithic GraphQL schemas. Apollo Federation is the most widely used implementation, allowing organisations with multiple teams to evolve their schema independently while presenting a consistent API. However, implementing federation requires careful planning and coordination between teams, and the added complexity can be significant. For instance, we had to develop a custom schema registry to manage the different subgraphs and ensure that the unified API remained consistent.
However, maintaining a federated graph comes with significant operational complexity, which must be carefully weighed against the benefits of GraphQL federation.