GraphQL vs. REST, 5 years in production
Initially made public by Facebook in 2015, GraphQL became popular in the late 2010s, especially thanks to its adoption by major players (GitHub, Shopify, Magento) and the maturity of the surrounding ecosystem.
GraphQL quickly won over developers, many of whom were already using REST APIs, by simplifying or enhancing the experience:
- Strict typing and standard definition schema (SDL) with introspection
- Flexibility: operation-oriented rather than resource-oriented
- Standardized ecosystem (e.g., Apollo GraphQL)
- Performance: retrieving only the necessary data
GraphQL's success is also due to the definition of standards, whereas REST/HTTP APIs are more often a set of best practices (resource-oriented, using all HTTP capabilities, etc.).
However, as I discovered after more than 5 years of using GraphQL in production, not everything is as perfect as it seems.
One of the great benefits of REST APIs is the ability for intermediary components (reverse proxy, API Gateway) between the client and the server to understand what's happening and make decisions or act on the request based on a route (/api/product/1234
, for example):
- Caching (e.g., CDN)
- Dynamic routing
- Security (WAF, authentication, rate limiting)
- Logging
GraphQL consumption relies on using a single endpoint, usually /graphql
, most often in POST
. As a result, all the possibilities listed above become difficult or impossible in middleware (e.g., API gateway), without at least knowing the GraphQL schema and being able to parse the incoming request.
Thus, your HTTP logs, usually used to monitor the health of your applications, will contain only POST /graphql 200
lines with GraphQL, regardless of the type of request (query or mutation) and whether an error occurred or not.
It's worth noting that it's entirely possible to use the HTTP GET
method, where the GraphQL query will be passed as a query string parameter, but this brings other constraints:
- Limit on the length of query params (2048 characters for a URL in Google Chrome)
- Limited readability of logs with URL-encoded requests
Although GraphQL offers real performance gains, allowing clients to retrieve only the data they need, which are not the same for example on a list page and a detail page, this comes not without risk:
- The "N+1" problem: in the case of retrieving entities related to an entity N (for example, the comments of a blog post), if 10 blog posts are retrieved, this will require 11 server-side requests (1 to retrieve the posts, and 10 to retrieve the comments for each post individually). This problem can be circumvented by using mechanisms such as Data loaders or more generally batching and caching mechanisms, which add extra complexity
- The unlimited depth problem: following the same example, suppose that the "Comment" type also allows retrieving its parent "Post". It then becomes possible to perform an almost unlimited loop query, impacting server-side performance (see example below). This can be limited by limiting the depth of requests or other mechanisms like whitelisting (see https://www.apollographql.com/blog/securing-your-graphql-api-from-malicious-queries), or persistent queries
# Example from Apollo GraphQL documentation of unlimited depth problem
query maliciousQuery {
thread(id: "some-id") {
messages(first: 99999) {
thread {
messages(first: 99999) {
thread {
messages(first: 99999) {
thread {
# ...repeat times 10000...
}
}
}
}
}
}
}
}
On the other hand, REST APIs (their implementations) have been criticized in recent years, often in favor of GraphQL (or tRPC https://trpc.io/). From my perspective, this is due to:
- The absence (intentional) of standards and heterogeneous implementations
- The impossible attainment of the state of the art, and its many definitions
Since REST is an architectural style, the key is to be inspired by it and take what you need to design your API. It doesn't make sense to strictly adhere to an utopian state of the art. We can then talk about REST-Like APIs: nothing prevents using the POST
method to retrieve data, which is often the case for a /search
endpoint, for example, which will accept a search query in its payload.
REST/REST-Like APIs also have numerous disadvantages:
- Absence of a standardized format for requests/responses or error management, although some conventions exist like JSON:API
- During read (
GET
) operations, passing filters as query parameters can be limiting: size limit (as mentioned above), presence of sensitive data (which will appear in HTTP logs); note that technically nothing prevents passing a body (in JSON, for example) in aGET
request, but this is is widely considered unconventional and not recommended due to inconsistent support across web infrastructure - Distinction of HTTP error codes between those returned by the server and the final application: does a 404 mean that the resource was not found, or that the requested endpoint does not exist? A good practice is to use only 400 codes, and to have a standardized explicit error format
- Retrieving the entirety of data on each call, impacting server-side performance (retrieving all data) and client-side (data transfer size and processing). This can be resolved by using a mechanism similar to serialization groups, with a query param
?version=<simplified|full>
, for example, or using thefields
query parameter, defined in the JSON:API specification of sparse fieldsets
In summary, here are the advantages I identify of GraphQL and REST/REST-Like APIs:
- GraphQL
- Standardized format
- Mature tools (e.g., Apollo GraphQL)
- Performance
- Subscriptions (using Websockets) to get real-time updates
- REST/REST-Like
- Simplicity
- Documentation / specification (OpenAPI schema) with complex types
- Possibility to benefit from intermediary layer features (CDN, API Gateway, reverse proxy): caching, routing, security, etc.
As often in computing, no solution is perfect. The essential thing is to use the solution that best suits your needs and will support you in the evolution of your application.
Moreover, REST and GraphQL APIs can perfectly work along, where GraphQL can be used for complex queries and aggregations while using REST for simpler, cacheable resource accesses.
I'm Michael BOUVY, CTO and co-founder of Click&Mortar, a digital agency based in Paris, France, specialized in e-commerce.
Over the last years, I've worked as an Engineering Manager and CTO for brands like Zadig&Voltaire and Maisons du Monde.
With more than 10 years experience in e-commerce platforms, I'm always looking for new challenges, feel free to get in touch!