Table of Contents
Production Ready GraphQL
Building well designed, performant, and secure GraphQL APIs at scale
Marc-Andre Giroux
Preface
First of all, thanks for purchasing this book. I hope you have as much fun reading it as I had writing it. GraphQL is still a very recent technology but is being adopted more and more every day. Ive always thought there wasnt much information available on how to build great GraphQL servers. A lot of the content seems to be focused on GraphQL clients, where GraphQL really shines. For that to happen, we need GraphQL servers to be ready. Over the past 4 years, Ive been helping to build GraphQL platforms at Shopify and GitHub, probably two of the biggest public GraphQL APIs out there. This book tries to put into words what Ive seen work best and what to avoid.
Enjoy!
Acknowledgments
This book wouldnt have been possible without all the great people Ive worked with on GraphQL over the years. Specifically thanks to Robert Mosolgo, Scott Walkinshaw and Evan Huus.
Special thanks to Christian Blais and Christian Joudrey, who gave me a chance at Shopify years ago, and to Kyle Daigle who gave me the opportunity to bring my GraphQL experience at GitHub.
Of course, this book wouldnt have been possible without my wife Gabrielle who has supported me every step of the way.
Illustrations: Gabrielle Le SauteurProof reading: Joanne Goulet
An Introduction to GraphQL
Just a few years ago, way before anyone had heard of GraphQL, another API architecture was dominating the field of web APIs: Endpoint based APIs. I call Endpoint-based any APIs based on an architecture that revolves around HTTP endpoints. These may be a JSON API over HTTP, RPC style endpoints, REST, etc.
These APIs had (and still have) several advantages. In fact, they are still dominating the field when it comes to web APIs. There is a reason for this. These endpoints are usually quite simple to implement and can usually answer very appropriately to one use case. With careful design, Endpoint based APIs can very well be optimized for a particular use case. They are easily cacheable, discoverable, and simple to use by clients.
In more recent years, the number of different types of consumers of web APIs has exploded. While web browsers used to be the main clients for Web APIs, we now have to make our APIs to respond to mobile apps, other servers that are part of our distributed architectures, gaming consoles, etc. Even your fridge might be calling a web API when you open the door!
Endpoint based APIs are great when it comes to optimizing an exchange between a client and a server for one functionality or use case. The tricky thing is that because of that explosion in client types, for certain APIs that need to serve that many use cases, building a good endpoint to serve these scenarios became more complex. For example, if you were working on an e-commerce platform and had to provide a use case of fetching products for a product page, you would have to consider web browsers, which may be rendering a detailed view of products, a mobile app which may only display the product images on that page, and your fridge, which may have a very minimal version of the data to avoid sending too much on the wire. What ends up happening in these cases is that we try to build a one-size-fits-all API.
One-Size-Fits-All
What is a One-Size-Fits-All API? Its an API that tries to answer too many use cases. Its an API that started optimized like we wanted and became very generic, due to the failure to adapt to a lot of different ways to consume a common use case. They are hard to manage for API developers because of how coupled they are to different clients, and sometimes of how messy it gets to maintain them on the server.
This became a fairly common problem with endpoint-based APIs, sometimes blamed only on REST APIs. (In reality, REST is not specifically to blame and provides ways to avoid this problem.) Web APIs facing that problem reacted in many different ways. We saw some APIs respond with the simplest solution: adding more endpoints, one endpoint per variation. For example, take an endpoint-based API that provides a way to fetch products, through a products
resource: GET /products
To provide the gaming console version of this use case, certain APIs solved the problem in the following way:
GET api/playstation/productsGET api/mobile/products
With a sufficiently large web API, you might guess what happened with this approach. The number of endpoints used to answer variations on the same use cases exploded, which made the API extremely hard to reason about for developers, very brittle to changes, and generally a pain to maintain and evolve.
Not everybody chose this approach. Some chose to keep one endpoint per use case, but allow certain query parameters to be used. At the simplest level, this could be a very specific query parameter to select the client version we require:
GET api/products?version=gamingGET api/products?version=mobile
Some other approaches were more generic, for example partials:
GET api/products?partial=fullGET api/products?partial=minimal
And then some others chose a more generic approach, by letting clients select what they wanted back from the server. The JSON:API specification calls them sparse fieldsets:
GET api/products?include=author&fields[products]=name,price
Some even went as far as creating a query language in a query parameter. Take a look at this example inspired by Googles Drive API:
GET api/products?fields=name,photos(title,metadata/height)
All the approaches we covered make tradeoffs of their own. Most of these tradeoffs are found between optimization (How optimized for a single use case the endpoint is) and customization (How much can an endpoint adapt to different use cases or variations). Something well discuss more deeply during this book.
While most of these approaches can make clients happy, theyre not necessarily the best to maintain as an API developer, and usually end up being hard to understand for both client and server developers. Around 2012, different companies were hitting this issue, and lots of them started thinking of ways to make a more customizable API with a great developer experience. Lets explore what they built.
Lets Go Back in Time
Netflix
In 2012, Netflix announced that they had made a complete API redesign. In a blog post about that change, heres the reason they stated:
Netflix has found substantial limitations in the traditional one-size-fits-all (OSFA) REST API approach. As a result, we have moved to a new, fully customizable API.
Knowing that they had to support more than 800 different devices, and the fallbacks of some of the approaches youve just read, it is not so surprising that they were looking for a better solution to this problem. The post also mentions something crucial to understanding where we come from:
While effective, the problem with the OSFA approach is that its emphasis is to make it convenient for the API provider, not the API consumer.
Netflixs solution involved a new conceptual layer between the typical client and server layers, where client-specific code is hosted on the server.
While this might sound like just writing many custom endpoints, this architecture makes doing so much more manageable on the server. In their approach, the server code takes care of gathering content (fetching data, calling the necessary services) while the adapter layer takes care of formatting this data in the client-specific way. In terms of developer experience, this lets the API team give back some control to client developers, letting them build their client adapters on the server.