Why we decided to transition to GraphQL
When we started building Courier, we investigated GraphQL, but the options for running a serverless version of Apollo (the technology we wanted to use) were limited and less stable. Because we don’t run EC2 or have dedicated servers, that was a major consideration for us. However, that’s changed quite substantially since we first looked at Apollo. Since then, we’ve been able to start transitioning both our internal and external APIs to GraphQL.
I’ll explain the reasoning behind this below.
Limitations of using REST
REST has been around for a long time, and today it’s still the most widely-accepted way to write an API. REST is a specification that sits on top of HTTP. API calls are structured around objects (like profiles, preferences, and templates) using dedicated HTTP endpoints. For example, if you wanted to expose a way to programmatically manipulate your user profiles, you might have a REST endpoint /user/{userId}
which can be queried to perform CRUD operations using HTTP GET
, POST
, DELETE
, etc. Writing a REST API is pretty straightforward – but REST can be tricky to use as an API consumer.
First, REST wasn’t designed for complex sequences of operations that don’t fit neatly into the CRUD bucket. It’s not easy to update two objects at the same time, for example, and even retrieving data in certain scenarios can require multiple calls and branching logic as one endpoint might have to call another one. Another downside of REST is it puts a lot of responsibility on the API consumer (which may be your internal developers or your customers) to know how the underlying data is structured. That’s not optimal for several reasons.
The API calls aren’t oriented to the common actions that the user wants to take. They’re structured rigidly around your objects. That means someone might have to call the same REST endpoint to set a label and add a collaborator, even though these are two completely different use cases. Another reason it’s not a good idea to structure your API around how your data is organized is because things change. Changes to your data are inevitable and it’s hard to adapt REST APIs to these changes (if you do find yourself in this situation, here's how we approached standardizing our REST API).
Advantages of moving to GraphQL
GraphQL is a query language with a very developer-friendly approach to building APIs. It's based on the idea that the API consumer shouldn’t have to know anything about how the data is stored internally. Instead, you describe your data’s relational schema and the consumer can query that nested data from a single endpoint that never changes. GraphQL also conforms to the idea of CQRS, or command-query responsibility separation – put simply, it means the way that you query data is different from the way you mutate data.
One of the things I like best about GraphQL is, as a side effect of implementing it, you’re forced to live by some of those rules of software engineering that you really should be living by. You have to think about your data holistically and you don’t end up with a bunch of poorly-designed endpoints lying around as the result of shortcuts you took to meet deadlines.
Because of how it’s built, GraphQL is really good at versioning: you can mark functionality as deprecated and you can change the underlying infrastructure without breaking existing integrations (and without the consumer even knowing). GraphQL also has a solid caching layer, which reduces our total operational costs because we end up not hitting our database as much. Because we’re a serverless shop, we will actually be implementing our caching layer through ElastiCache.
Using GraphQL at Courier
How we decided which technology to use
As I mentioned earlier, we thoroughly researched the options for implementing GraphQL and kept an eye on possible solutions. There were two main options that emerged for our use case: AWS AppSync and Apollo GraphQL.
We evaluated AppSync because we’re an AWS customer, we use cloud formations, and it was appealing to be able to stand something up quickly. But there were some core security choices we made when implementing multi-tenancy in Amazon Cognito that made the switch to AppSync difficult. We realized AppSync wasn’t going to work for us unless we changed some of those fundamental decisions.
But that wasn’t the only reason we decided to go with Apollo. Compared to AppSync, which uses the Apache Velocity Template Language (VTL), Apollo is just JavaScript. When we work with Apollo, we don’t have to do a lot of the mental context-switching that happens when you use different languages. Not to mention, Apollo is popular for a reason: it’s a rock-solid product that’s constantly evolving and has a growing and supportive community of users. Finally, we chose Apollo for the Apollo Federation, which will help us grow our Graph without affecting our performance as our product scales.
Our roadmap for transitioning to GraphQL
Right now, we’ve moved some of our internal APIs to GraphQL, such as the infrastructure for accessing users and tenants. We’re also building all new features with GraphQL as well.
While it'll be some time before we move all our internal APIs to GraphQL, we have plenty of important candidates for this transition. One key use case is autosave during template creation in our notifications designer. When you're editing a template, you can add content blocks like text or images, add notification channels like email or SMS, and add conditional logic (just to name a few examples) and, as soon as you make a change, it gets autosaved. Behind the scenes, these edits are funneled through a common processor.
One of the problems in REST is it’s difficult to do partial updates. The various components end up having to send the whole template resource when they want to update a single field. Sure, you can implement PATCH endpoints, but those come with their own complications. When you factor in doing validation on the full object with every call, autosave has the potential to become an expensive operation. Moving autosave operations to GraphQL mutations will help us solve this problem outside the constraints of a traditional REST API design and more closely represent the types of actions our users are taking.
As we move all our internal infrastructure to GraphQL, our ultimate goal is to expose a GraphQL interface to our customers, along with an explorer that will make it so consumers can interact with our schema right from the browser.
If you’re interested in working with GraphQL, we’re hiring engineers at Courier. You can check out our open roles here – we hope to hear from you!
Top comments (5)
I don't fully understand how GraphQL mutations alleviates any of the concerns outlined with PATCH endpoints, could you explain that a bit further? I'm interested in graphql and am about to dive neck deep into it, but have concerns about how to live long term with graphql while requiring relational data in MySql for example
The issue with PATCH is that there is no official RFC that states exactly how a PATCH should be performed, but there are sources that recommend one pattern over another, like a transaction-pool. I assume where the PATCH alleviation comes in is that GraphQL provides a uniform way to perform partial updates, which takes some of the burden of implementing custom PATCH endpoints off of your shoulders.
That's kinda weak imo; but if you find value in that and it works for your products n projects, great! More power to ya! I'd love future posts comparing some common product focused use cases between graphql and rest, that'd help me grok it more.
<3<3
I don't use GraphQL personally or in a professional capacity, so I can merely speculate as you are, but ultimately I have had semantical debates with others on the proper way to patch, so in some ecosystems having conflicts like that removed makes sense. I agree that it's not enough to convince me to change. What can, however, is if it dynamically includes only the requested properties and relationships based on the data model requested in the request, which I think it does. If you care about your payload sizes and mitigating data load on mobile devices, I can see where GraphQL would excel in that regard.
I agree that seeing use cases makes the persuasion that much more convincing.
I love it! Would love to learn more about how this looks like under the hood. The more I learn about GraphQL, the better it sounds.(: especially the mutations of only specific columns vs the entire resource each save.