It came to my attention that many perceive GraphQL as a complex way to communicate with an API compared to REST. It seems there is a perception that REST is somehow simple and easy, and I think it's really not.
Let's compare apples to apples
When we compare GraphQL to REST, we usually don't compare the same thing because we take a simple REST API setup and compare it with a complex GraphQL setup.
To really compare adequately, we need to compare either a very simple setup in both cases or an equally complex setup. In this article, I am going to simplify the GraphQL setup to the level of a simple REST.
First of all, what's the biggest thing that makes you think GraphQL is complex? My bet is the client-side code that most people use. Let's remove it.
Let's take this GraphQL API https://api.spacex.land/graphql/ and try to avoid using any abstractions to fetch some data. GraphiQL UI gives us a way to learn what is being sent and received very easily, so I just need to grab the query from network UI in the Browser DevTools.
It sends an XHR request using POST to the endpoint. The GraphQL query of the request is this:
launchesPast(limit: 2) {
mission_name
launch_date_local
rocket {
rocket_name
}
}
This query is supposed to fetch a list of past SpaceX Rocket launches, limit the results to 2 items, and return mission name, launch date, and rocket name.
To actually send this request using built-in browser API, I would need to do this:
const endpoint = 'https://api.spacex.land/graphql/'
const method = 'POST'
const headers = {'content-type': 'application/json'}
const body = JSON.stringify({
query: `{
launchesPast(limit: 2) {
mission_name
launch_date_local
rocket {
rocket_name
}
}
}`,
variables: null
})
const response = await fetch(endpoint, {method, headers, body})
const json = await response.json()
The JSON result we get looks like this:
{
"data": {
"launchesPast": [
{
"mission_name": "Starlink-15 (v1.0)",
"launch_date_local": "2020-10-24T11:31:00-04:00",
"rocket": {
"rocket_name": "Falcon 9"
}
},
{
"mission_name": "Sentinel-6 Michael Freilich",
"launch_date_local": "2020-11-21T09:17:00-08:00",
"rocket": {
"rocket_name": "Falcon 9"
}
}
]
}
}
Let's see what we have to do to get the same result from a REST API by using browser APIs only. Since I don't really have the same API, I will just imagine what it would be if we had it:
const endpoint = 'https://api.spacex.land/launches/'
const method = 'GET'
const headers = {'content-type': 'application/json'}
// We use URLSearchParams here for the sake of encoding.
const query = new URLSearchParams()
query.append('limit', 2)
query.append( 'filter', [
'mission_name',
'launch_date_local',
'rocket.rocket_name'
])
const response = await fetch(`${endpoint}?${query}`, {method, headers, body})
const json = await response.json()
I am sure you would agree that the complexity between these two examples is very similar if we really compare the same thing from the client setup code perspective.
A hidden difference
When we compare a primitive type of usage, they seem very similar. There aren't many benefits for one over another, though when you look closely, you will recognize that even here, GraphQL has solved a few big problems already:
GraphQL dictates a single way of requesting the data. You don't have to agree with your team on when to use which HTTP method, query-string, or paths, how to structure the URL, how to specify additional parameters like the limit of results and fields to return. Even in this primitive example, you can see that GraphQL, as the name implies, is a language where REST is just a pattern that is flexible enough to allow your team to build inconsistent and messy APIs.
The shape of the query is also the shape of data you will receive quite literally. This is another missing piece in the REST contract where you usually have no way of specifying the shape of the data, while in practice, a change in the shape will likely break your application.
The core reason why we need GraphQL
We often talk of HTTP APIs as some simple data fetching mechanism. You send a request, you get some data - easy enough, sure. We really fail to mention that it's not just some data most of the time, it's a contract.
API is a contract between two parties that is supposed to specify how exactly I can get and send the data, what data in which shape I am going to receive, and finally, what type it is gonna have. Potentially you can have all that with REST too, but that's where GraphQL helps you to do it consistently.
It has become super relevant as we entered the age of distributed systems and microservices. A complex application consists of dozens or hundreds of APIs, some of which are often provided by a 3rd party. When all of them vary in the level of safety and precision, how robust is your application going to be in the end, really? How many bugs do you think are created because of weak contracts? 10%? 20%? 30%?
Advanced usage
While you can use GraphQL for very simple use cases - you still get many benefits, but it doesn't stop there. It offers solutions for mutating data, describing types of data, conditional query logic, composing data from multiple sources, and much more. It really shows the difference between a language and a pattern.
Despite that GraphQL is best for dynamic data, it's still possible to implement caching and avoid slow responses for complex schemas.
For example, a startup GraphCDN created a caching layer on top of CDN that works with any GraphQL API implementation. It is only possible because GraphQL makes you specify everything that is needed by design to allow smart caching.
Not only is GraphCDN able to avoid doing unnecessary computation on your application servers - it does so using edge computing. That means a client has a much shorter response time after the initial response was cached because the client will receive a response from a server geolocated nearby.
Top comments (3)
Thanks for the writeup. I have to fully agree.
I actually fell into that trap for a client project of mine. It was meant to be a fairly small project which was meant to be completed within a few weeks. In order to keep it "simple" and keep the barrier to entry as low as possible should someone else take over, I've went with a fairly classic REST approach.
Now it's a year later, I'm still maintaining it, the app has grown a lot and my REST endpoints have become a mess ๐ .
So I'd argue that another benefit of GraphQL is that it forces you to be more explicit about your underlying schema whereas REST gives you a lot more freedom to shoot yourself in the foot.
But one of the things that makes GraphQL complicated is having to specify the fields you want returned, and having to write out your object structure as a string all over your code.
I agree that gql makes the API contract more explicit, but it doesn't make it any harder to break. Whether your 3rd party API provider makes a breaking change to their REST or gql API, either way your client code is still broken.
Any REST API should follow the REST conventions for naming, routes, verbs etc. Nothing forces you to follow them, but, nothing forces a GQL
mutation
to actually mutate anything. Nothing stops you making db changes during aquery
.Not to mention:
By the way, an alternative REST API that optionally lets you specify the returned fields, as well as filtering, is OData. In that case, your REST URL would be eg
/launches?$top=2&$select=mission_name,launch_date_local&$expand=rocket
In Rest API you can define the shape, too and it is simpler in the server - define the path, make query to DB, for the example. In the front-end you don't need any library to generate the POST request and often, you can debug GET requests just with the browser.