Why would you want GraphQL
Or how frontend engineers tricked backend engineers into doing more work 😅
Too Long; Don’t Wanna Read
GraphQL is kind of like a pick up truck. If you research one a little and ask yourself, "why would I bother to fit this gas-guzzler in my garage?"
Then it's probably not for you.
On the other hand, if you need a pick up truck, you'll feel it by the end that you need a pick up truck.
My Assumptions About You
Have made or used a REST API at least once
Briefly looked over GraphQL's website
Know generically that GraphQL is meant to replace REST APIs in some way
But you don't quite understand why you'd want to, because REST APIs work just fine in your mind
Anatomy of Hello World GraphQL
Unlike a REST API, where you sort of pinky promise that your API will give a certain result, GraphQL enforces this your API contract by forcing you to write out a schema with its special syntax.
Then you go off and write code to interact with a library of your choice in a language of your choice. In general most of them parse this schema and expect you to resolve the functions declared in the schema to an actual function in your code.
Schema
This represents the most basic complete thing you can have in a Schema. A function named hello
requires a named argument of type string called name
and returns a string.
type Query {
hello(name: String!): String
}
Query
Queries in are always POSTs in GraphQL. So you would shove this query below into a POST down to a single endpoint. It’s often called /graphql
.
query {
hello(name: 'Robert')
}
Response
You will get back JSON that is in the structure you asked for under a data
element.
{ data: { hello: 'Hello Robert!' } }
What is GraphQL Trying to Solve?
I’m going to be talking about Github as an example for most of this article from here on out. I’ll compare an example from interacting with Github’s 2 different APIs, REST and GraphQL.
Number of Client Roundtrips
GraphQL cuts down on the number of API calls from client to server. The vast majority of time the server-to-server calls request times are a tiny fraction of the client-to-server calls, so this means you can easily experience a more responsive application just by cutting down calls.
REST
Get your first 10 repos
Get their owners and their first 2 repos
1 Call to server:
GET api.github.com/user/repos?per_page=10
then 10 calls to server:
GET api.github.com/users/{username}/repos?per_page=2
GraphQL
1 call to server:
{
viewer {
repositories(first: 10) {
nodes {
name
owner {
login
repositories(
first: 2,
) {
nodes {
name
}
}
}
}
}
}
}
Timing Comparison
Assuming 200ms from client-to-server, this is the comparison:
REST: 2 rounds of calls >400ms or 11 calls in series for >2.2s
GraphQL: 1 call at ~200ms
Considering where user expectations are:
Now admittedly 2 seconds is just the page load time, the bar goes up for user interactions to 100ms.
100ms is the threshold “where interactions feel instantaneous.” ~ Paul Buchheit, Gmail Creator.
REST API Object Nesting
Everything in this example above is why there’s a continuous push from frontend engineers to deeply nest data into a REST API endpoints. Usually this falls out in two different ways.
Option 1: The user or account endpoint becomes a God object and basically contains every possible relationship to all data whether or not it’s needed.
Option 2: Multiple endpoints get a large variety of optional parameters to expand the resulting payload and get more data in one go.
Deeply nested objects are bad for many reasons. Chief among them is that it’s impossible to anticipate everything you'd want on every endpoint. You can't keep shoving in everything related to everything else in every endpoint.1
Another bad reason is slightly more obvious. The size of the payload potentially being returned.
Amount of Data Returned
Extending our Github API comparison, let’s consider getting the first 100 repos for a logged-in user.
REST "query"
api.github.com/user/repos
GraphQL Query
query {
viewer {
repositories(
first: 100
) {
nodes {
name
}
}
}
}
If I go run these two queries on my repos, the REST API returns 171kb whereas the GraphQL return 1.8kb. That's a 100x payload savings.
Now if the requests where even larger, you can see how this could impact speed, but I want to point out why this matters for companies with large amounts of traffic.
If this query is made 1000 times per second over an entire month,2 the REST endpoint will have egressed out ~440TB in data, whereas the GraphQL endpoint will have egressed ~ 4TB.
Let’s say Github is using Azure’s public egress pricing, and to slightly simplify the math for our discussion, we’ll say Microsoft gives them a discount to use the lowest public pricing on all data, $0.05 per GB flat over 100 GB. Napkin math says that’s $20k for ~400TB vs $200 for ~4TB per month.
Now this is chump change for Github. But it may not be for you, and also, in many areas of the world data for the end user is still very expensive or relatively slow.
So this payload size can become a differentiator for better user experiences related to speed in a different way.
Minimize Client-Side Data Manipulation
GraphQL touts the ability to get data into a shape that's useful to you in the first place, and not needing to reshape it to use it.
Let’s go back to the Github example again. I’ve changed the requirements to add a few more in bod:
Get your first 10 repos that are forks.
Get their owners and their first 2 repos for each of them that they own (aren't forks).
Order their repos by highest number of stargazers first
Let’s do the REST API first again.
It’s one call to this endpoint again, right?
GET api.github.com/user/repos
No, the REST API has no "is_fork" param. So write a bunch of code to page over all of your repos, filter out those in JS that are forks, and from that list extract out the owners of each of those repos.
Now after that you can call this endpoint 10 times for each of those repos, right?
GET api.github.com/users/{username}/repos
Well, not quite again, the REST API has no "is_fork" param, nor an order by for stargazers. So write a bunch of code to page over all of the original owner’s repos, filter out those in JS that are forks, and then resort the remaining by stargazers.
Then merge the two datasets together somehow, and you’re done! So simple!
I’ll just leaving writing all that JS as “an exercise to the reader.”
Now here is that query in GraphQL:
{
viewer {
repositories(first: 10, isFork: true) {
nodes {
name
owner {
login
repositories(
first: 2,
isFork: false,
orderBy: {field: STARGAZERS, direction: DESC}
) {
nodes {
name
}
}
}
}
}
}
}
Yeah, that’s much simpler and cleaner to understand, and requires very little manipulation to get what you want.
Documented and Functional Schema Parity
Unlike various specs like OpenAPI where there can be and likely is a difference between documentation and the API behavior, GraphQL doesn’t work that way.
The GraphQL API spec and the running API aren’t different things. They’re the same thing. It’s similar to how when you ask Postgres or MySQL for a table schema; you don’t have to wonder if it’s true or not.
If you want to experience it, go ahead and login to Github and try it out their explorer here.3
GraphQL really shines here, and this is the stuff that makes frontend engineers really love GraphQL compared to a REST API.
You can look around and find stuff. You get autocomplete and a real browsable API and preflight checks for API calls. All of this is for free, because it’s a schema-first API.
All of this stuff for REST APIs is either very hard to do or just not done.
Common Complaints about GraphQL
It's complex
Complaint: much more verbose and harder to use for simple cases
My Opinion: a bit hyperbolic. It's not always much more verbose, and I'm sure I can find longer REST queries. But yes, it is more verbose on average.
Additionally, once you learn GraphQL, you've learned the syntax entirely. Unlike REST which has different flavors--even within the same API provider.
It's overkill
Complaint: People usually don't need all this. REST usually solves their issues. It's simpler to understand and implement from the server side.
My Opinion: you're right. You might not need this. Frontend devs who tried both GraphQL and REST seem to prefer GraphQL, because they can rely on it being far more consistent of an experience.
If you're a full stack or backend dev, this may all seem like a whole lot of work and complexity, because it is for you. You have both sides of the problem in your head already; making your write it twice (or more) seems redundant.
It's incomplete
Complaint: It doesn't even solve everything you need to do like error messages/codes, authorization, file transfers, or even pagination!
My Opinion: you're somewhat correct. It mentions many of those things, and gives advice on how to solve them. But you're correct that it doesn't solve them in their spec. But for that matter, REST doesn't have a spec either. It just has conventions or expectations so in that sense it's no worse than REST.
Conclusion Again
GraphQL is kind of like a pick up truck.
If you look at it and ask, "why you'd want to fit this gas-guzzler in your garage". Then it's probably not for you.
If you've now seen what it can give you, and you're like, "OH, I CAN TOTALLY USE THAT!" Then it's probably for you.
Secret Conclusion
Software isn't as exclusive or strict as buy a car or a pick up.
Every company large enough to have a public GraphQL API to my knowledge also maintains its REST API.
Why do they have both?
Their GraphQL API usually is just calling their REST API under the hood. And building REST APIs are easier and more direct to understand on the backend.
For internal APIs, should you build both?
I don’t know that’s very context dependent. If you’re a team of 3-5 full stack engineers, then maybe not. If you’re a team of 50, then maybe you should listen to what the frontend engineers want as an experience to make their lives easier.
Yes, I realize that this is exactly what GraphQL is doing. It’s just literally giving up and saying: OK, you get one endpoint now. Be very explicit about what you want.
This seems very low in my mind for Github, but I don’t know of any public data to verify this one way or another.