When I Googled “what is GraphQL” to learn more about the network protocols, all I saw was a comparison between REST and GraphQL. Most of the conclusions said, “use GraphQL.” It felt very binary (and trendy, for that matter), which is a problem because each product and use-case is unique. The fact is, whatever is newest and shiniest gets recommended more loudly. But you have to weigh the trade-offs and come up with a solution that is best for your situation.
There is a general understanding that either REST is better than GraphQL or vice versa. But the truth is they both address different problems and have different strengths and weaknesses. The question isn’t necessarily which one is better to use, but which one is better to use for specific circumstances. The best way to evaluate GraphQL, REST, or any other technology is to figure out your constraints based on the problem you are going to solve.
REST is the popular option, and for good reason: it’s quick to learn and implement. No one wants to write complex code if they don’t have to, so it’s not hard to see why quick and easy REST beats complex and clever GraphQL in most situations.
Two aspects of REST really shine. The first is that most ORMs (object–relational mapping) are optimized to work with REST, so that is not a problem you will have to solve. The second is REST is ideal for quick transactional requests, like the following:
Hey Courier, here is the payload (data), and I want to send a notification.
Hey Courier, what is the status of the last message I sent? Here is the ID since you don’t remember anything (stateless protocol so every request would provide precise input to query the state).
Additionally, with REST, you share the complexity between client and server. It has the benefit of proven patterns on how to do something, and it is definitely a well-proven, time-tested way of writing applications.
GraphQL is a different approach entirely, and it takes time and effort to create harmony. A system that is ideal for all your use cases will demand a lot of your time and will require your team to learn a lot of new concepts. It will force you to think in a certain way. We knew the cost of implementing GraphQL was probably going to be exhaustive — we expected the move was not going to be a do-it-once-and-forget-it thing.
The default option would be REST, and it’s a good one. Not just because it’s easy, but because there is an expectation of shared familiarity. Developers are likely to already know REST, whereas the same is not true for GraphQL. Unless you’ve identified needs that GraphQL is especially good at solving, REST is the default choice for a good reason. We would rather rest easy with REST, build something that both excites and helps our users, and get their product in the hands of their friends and family with time-tested methods.
Let’s get right into it. With REST, there’s a lot of back and forth and manual work. Calls can result in either over-fetching or under-fetching based on the API contract. For example, if you end up with URIs (uniform resource identifiers) and not the specific data you’re looking for, the network calls you need to make on your client to get what you need escalate quickly.
Whereas GraphQL gets exactly the data you want on an API call. You have control over the query on a granular level, which is not something you can easily do with REST since it’s not made for that specific purpose. Having this granular control will allow you to have fewer network calls and will require fewer developmental changes on client applications, since responsibility has been shifted to backends.
Applications can be informationally and visually heavy and also need to retrieve a variety of interconnected data. Fewer network calls in this scenario can be crucial for performance when this data needs to be retrieved in what are potentially many different and unknown situations. With the right implementation of GraphQL, you can eliminate the ability to over-fetch.
If your application is reading 1MB of data locally, it can finish the job in 0.4 milliseconds — as opposed to reading it over the network, which can take 150 milliseconds — a drastic difference. The point here is you want to optimize for fewer network requests. REST doesn’t give you those affordances. If optimizing for network requests is the most important factor, GraphQL would work better for you.
With GraphQL, you’ll get only the information you need. This is due to a self-documenting schema that’s easier to consume and has better tooling around consuming your endpoint.
Writing GraphQL queries is just the tip of the iceberg. Implementing a brand new solution with GraphQL comes with challenges to overcome and problems to solve. The GraphQL stack might appear to be simple to get started with, but it gets complex quickly. It requires a lot of upfront learning and can be intimidating for newcomers to figure out how all these pieces fit together. Before you move to GraphQL, you need to understand what you’re signing up for.
Since GraphQL is not built into an ORM, it means there’s a lot of architectural choices that need to be made for your stack that will require extra effort to set up. It also comes with a steep learning curve. Part of that learning curve is control. GraphQL inverts control and hands it over to the client. Any amount of information can be requested, which means building a third-party API can become burdensome if you have expensive queries.
We all know naming things is hard, and GraphQL makes this a little harder. The GraphQL schema is a list of object types. It’s more than just a catalog; it’s a snapshot of the API. In a very large codebase, this could lead to naming collisions, so thoughtful naming conventions are a must.
In addition to the learning curves we’ve found ourselves dealing with, caching, one of the most loved and hated topics in computer science, is a whole lot more adventurous with GraphQL. Caching can happen on the client or on the server. REST uses headers to control the caching. However, GraphQL doesn’t have this affordance at the HTTP level.
All POST requests in GraphQL automatically opted out of the free HTTP caching you get in the browser. That means you have to bring your own cache when you’re in GraphQL land. Not only is this caching solution something you have to build, but you also have to maintain it — a consideration that should be thought about thoroughly.
In the end, you want to build something useful. GraphQL and REST can get you there, but be mindful about what either option means for you and your team long term. We identified that GraphQL was a great solution for some of our needs.
The general wisdom is to choose either REST or GraphQL for your needs, but in reality, neither one covers all the bases. Our biggest motivation when building something is to evaluate how a developer would like using it. We started gradually introducing GraphQL first in our own internal APIs. We converted those endpoints to see how they worked for us internally before we exposed them externally.
But for Courier’s studio application, we went with GraphQL. Even though REST would allow us to build it, it wasn’t necessarily the best solution. It would require us to either implement (or add more) logic to handle new information and endpoints every time we decide to have new info on a page. Because the application has a growing need for data and information, and GraphQL solves this problem better for us.
Our backend data objects and business objects are rapidly becoming more interconnected, and GraphQL is a good way to relate each business object within our ecosystem. Managing all that in REST would be quite a task. And it comes with the downside of additional network requests.
GraphQL solved our constraints fairly well. So while conventional wisdom states we should move to GraphQL entirely, the fact is, for customer-facing APIs, we need REST. For our own frontends, however, GraphQL is the answer.
We decided to tackle the learning curve even though learning has uncertain pay-offs. Everything we do has opportunity cost, and early adoption of something like GraphQL can feel like cargo cult-like behavior. But in the end, GraphQL works for us, with our circumstances.
Courier frontend is growing rapidly with the need to present rich information coming from various sources. REST could work, but GraphQL would work even better because of the query-able, type-safe nature of GraphQL.
Let’s take a look:
Here is a type defined in our GraphQL. We know in the future this Notification node will have more information needed. Say, a use-case to present all the collaborators involved in building this notification. With GraphQL (based on our existing pattern), we can easily extend the Notification node and add an edge or connect it to User (collaborator), so this extensibility works out great for our use case.
Another big advantage of GraphQL is the fact we can choose what data to get from our backend. Let’s steal this analogy from Dan Abramov where he explains the new React 18 batch rendering feature. Let’s say you need to go shopping for breakfast items at the store. Not only would you ideally like to come up with a reasonable list of items before going to the store, but you’d also want to optimize the number of trips you need to take.
The grocery scenario is quite similar to client applications needing to make additional network calls to the backend, like every time there’s a need for additional fields. GraphQL solves this by providing a declarative, type-safe mechanism to query data from your backend.
It’s fairly easy to look at the query below and figure out what information you can get.
And tooling like introspection allows us to go even further! Introspection lets you gather information about supported queries in the GraphQL schema — you can get autocomplete schema definitions with type information. Beyond that, strong-typing makes introspection easier. This can be accomplished using TypeScript interfaces and is what will allow you to specify what your data is as well as automate a lot of the documentation.
This declarative approach to query information resonated with us about why GraphQL is compelling to use. So we chose GraphQL for our client applications because it provides strong typing and structured queries. GraphQL has a specification that makes it intuitive to build applications.
We haven’t given up REST altogether, though — we’re still using REST for our external APIs. This is because they’re transactional in nature, and it isn’t advantageous for us to expose an external GraphQL endpoint just yet based on the simplicity of our REST endpoints.
These are some of the reasons we love GraphQL at Courier.
Since we are a developer API product, it’s expected that we support all formats, REST obviously included. REST is widely used, popular, easy to maintain, and has an easy triage way of exposing endpoints. We know how and what to measure in our REST endpoints. We have pretty decent tooling built around the REST ecosystem, and moving it to GraphQL won’t add much value to the user or the business right away.
But GraphQL works best for us moving forward with our client applications. Exposing Courier’s functionality to external developers, we will still be resting on REST. And we still have a decent amount (if not most) of Courier’s functionality exposed via REST endpoints.
We have been running GraphQL on NodeJS for more than one year at this point. It has increased our velocity to push new features as well as given us a significant performance boost in our Studio application. We get the advantage of the high bandwidth on the server side to pull data from GraphQL’s middle layer, which is responsible for stitching data from various data sources.
GraphQL is an exciting field — with a learning curve! But once you get the hang of it, specifications provide great tooling out of the box and saves a lot of code. Finding the balance between getting features out the door and adopting new technology can be a challenge. Finding the iterative approach that gives you time to adopt new technology would help you gradually increase your chances of success.
So there is no silver bullet that solves all the problems. New solutions are inspired by shortcomings in existing technology, but they come with new sets of challenges. Everyone’s situation is different, and contextualizing the solutions based on your problem space is the way to think when making these fun decisions.
In this post, we will start diving into the internals of Postgres to understand how replication works an...
January 20, 2022
In the bigger picture, observability ties your technological infrastructure to your overarching product ...
December 15, 2021