This post is a preview of my upcoming book on GraphQL schema design,  it provides an introduction to GraphQL by starting back in time to  understand what problems GraphQL is really trying to solve, and why it  was designed this way. If you’re interested in learning more about the  book, I would really appreciate if you signed up for the newsletter: https://book.graphqlschemadesign.com/

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 an endpoint based API any API using a technology or architecture that revolves around HTTP endpoints. These may be a JSON API over HTTP, RPC style endpoints, REST, etc.

These  APIs have several advantages, and in fact, are still dominating the  field when it comes to web APIs. There is a reason on why that’s the  case. These endpoints are usually quite simple to implement and usually  do one thing very well. With careful design, Endpoint based APIs can be  very well optimized for a particular use case, are easily cacheable, and  very 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 client for Web  APIs, we now have to make our APIs to respond to mobile apps, other  servers part of our distributed architectures, gaming consoles, hell, 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 started to be more complex. For example, if you were working on a 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 APIs

What  is a One-Size-Fits-All API? It’s an API that tries to answer too many  use cases. It’s 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 how  messy it is usually to maintain on the server.

This  became quite a common problem with endpoint based APIs, sometimes  blamed only on REST APIs (In reality, REST specifically is not to blame,  and provides ways to avoid this problem). Web APIs facing that problem  reacted in a number of different ways. We saw some APIs respond with the simplest solution: adding more endpoints. One endpoint per variation. Take for example an endpoint based API that provides a way to fetch products:GET /products

To provide the gaming console version of this use case, certain APIs solved the problem this way:

GET api/playstation/productsGET api/mobile/products

With  a sufficiently large web API, you can maybe 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 really generic approach, by letting clients  basically 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 google’s 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). We’ll cover this tradeoff more in Chapter X: Optimization vs Customization.

While  most of these approaches can make clients happy, they’re 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. This was the case for Netflix when they redesigned their API a few years ago.

Netflix’s Server Adapters

In  2012, Netflix announced that they had made a complete API redesign. In a  blog post about that change, here’s 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 you’ve just read, it is not so  surprising that they were looking for a better solution to this problem.

The post also mentions something that is really key 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.

Netflix’s  solution involved a new conceptual layer between the typical client and  server layers, where client specific code is hosted on the server:

https://medium.com/netflix-techblog/embracing-the-differences-inside-the-netflix-api-redesign-15fd8b3dc49d

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 own client adapters on the  server.

They liked their approach so much that they filed a patent for it, with the pretty general name of “Api platform that includes server-executed client-based code”.

To learn more about that approach, I highly suggest you read the whole blog post.

Soundcloud’s “Backend for Frontend”

Another  company struggled with similar concerns back then: Soundcloud. While  migrating from a monolithic architecture to a more service oriented one,  they started struggling with their existing API:

After  a while, it started to get problematic, both in regards to the time  needed for adding new features, and also due to the different needs of  the platforms. For a mobile API, it’s sensible to have a smaller payload  footprint and request frequency than a web API, for example. The  existing monolith API didn’t take this into consideration and was  developed by another team, unaware of the mobile needs. So  every time the apps needed a new endpoint, first the frontend team  needed to convince the backend team that this was truly the case, then a  story needed to be written, prioritized, picked, developed and  communicated to the frontend team.

Rings  a bell doesn’t it? This is very similar to the problems Netflix was  trying to solve, and the problems that can be caused by implementing the  customization solutions we talked earlier this chapter.

Their  solution to this was quite interesting: instead of including advanced  customization options to their main API, they decided that each use case  would get its own API server. When you think about it, it makes a  lot of sense, this would allow developers to optimize each use case  very well without needing to worry about other use cases, which an  endpoint based API performs really well at.

They called this pattern “Backends for Frontends” or BFF. A great case study from Thoughtworks includes a great visualization of the pattern:

https://www.thoughtworks.com/insights/blog/bff-soundcloud

As you can see, this makes each BFF handle one, or very similar use cases, which allows developers to write manageable APIs for one use case and avoid falling in the traps of writing a generic “One-Size-Fits-All” API.

Fast Forward to 2015

In September 2015, Facebook officially announced the release of GraphQL, which has since sky rocketed in popularity.

However, it might not be a surprise to you after the other solutions we’ve covered so far, but it is really in 2012 also, that Facebook started re-thinking the way they worked with APIs. They were frustrated by very similar concepts:

We were frustrated with the differences between the data we wanted to use in our apps and the server queries they required. We don’t think of data  in terms of resource URLs, secondary keys, or join tables; we think  about it in terms of a graph of objects and the models we ultimately use  in our apps like NSObjects or JSON.

There was also a considerable amount of code to write on both the server to prepare the data and on the client to parse it.

Once  again, frustration between differences in data returned for different use cases. However they also add that they had a different mental model for what Facebook should be represented as. The last sentence above once again shows the difficulty in maintaining and evolving an API that has to be generic enough to handle so many use cases and variations.

Enter GraphQL

If  you’re reading this book, chances are you’re already familiar with  GraphQL’s basic concepts. Even if you do, or don’t, it’s interesting to  look at the components of GraphQL with that history in mind.

GraphQL is a specification. Not a library, not a product, not a database. This specification  defines, within the larger GraphQL name, a few interesting parts:

  1. The GraphQL Query Language (Graph Query Language Query Language, sounds weird, I know,)
  2. The GraphQL Schema Language and type system
  3. An algorithm for how a GraphQL implementation should execute queries against a given type system.

What  is interesting with these three specification items is to look at how  they’re designed to answer some of the problems we covered in this  chapter so far. To me, there are three main things APIs had to be better  at in these contexts:

  1. Embracing different types of clients, different use cases
  2. Allowing clients to take more ownership in how the API server responds to use cases.
  3. Keeping a manageable system and offer a solid developer experience even with item number 1 and 2 in place.

We’ll cover each of those, and how the GraphQL specification tries to answer them.

Embracing different types of clients and use cases

We’ve  already seen different approaches for endpoint based APIs to provide  alternative versions of use cases and modifications based on the client.  GraphQL chose to go on the far end of the spectrum when it comes to customization. Remember this style:

GET api/products?fields=name,price,photos/url,sizeGET api/shop/1?fields=name,location/lat,long

This  is already quite a customizable API. If we push that concept even  further, we can create an even more complete query language, and even  drop the need for multiple endpoints:

GET api/graphql?query="{ shop { name location { lat long } products { name price { photos { url size } } }"

GraphQL  decided to push the customizability so far that it doesn’t even need  dedicated endpoints per use case, because the world of capabilities, of  use cases, is represented in a single “Graph”, which we can query using a  query language.

This query, in a easier to read format, is a good example of GraphQL’s power:

query {
  shop {
    name
    location {
      lat
      long
    }
    products {
      name
      price
      photos {
        url
        size
      }
    }
  }
}

In  a single query, we’ve traversed many relationships, and selected our  own, carefully crafted use case, specific to our client’s need. The  reason why our client knows this specific query is within the realm of  capabilities of our API server is because of GraphQL’s type system, the schema. We’ll focus on how this one is the next chapter.

Any  GraphQL API exposes a strongly typed system to clients, exposing the  entire set of possibilities to clients. If compared to REST, it could be  similar to starting from the root endpoint to discover links to  resources. It is machine readable, and human discoverable.

Many articles written on GraphQL focus on the fact that GraphQL solves the under-fetching and over-fetching problems. This  is totally true, but to me is a side effect of something much more  crucial: enabling any number of clients to coexist, and survive the  evolution, of a single API server.

GraphQL definitely embraces the difference in clients, in fact, it puts a lot more power in the hands of client developers than what we were used to we endpoint based APIs. By  giving clients total control on what shape of data they want to  receive, this coupling between the usual endpoint result and client  request is soften by a lot.

We had talked about allowing customizations earlier this chapter, and knew that it often resulted in a system that’s very hard to manage, and offers a poor experience for server developers, is that the case with GraphQL?

Keeping a manageable system and offering a solid developer experience

Netflix’s  client-based server adapters, and Soundcloud’s BFFs were able to give  customization to clients without losing a good developer experience on  the backend. GraphQL is no different. This is due to several attributes  of the GraphQL specification that makes client coexisting quite an  enjoyable experience for both client and server developers.

First, it’s very important to note that the GraphQL query language only allows selecting fields declaratively and explicitly. There is no SELECT * , no way of selecting all fields on a particular type, and this is for a very good reason. This allows server developer to add functionality, new fields, new use  cases without existing clients ever needing to care about these changes,  or ever be impacted by them. Each individual GraphQL query is selecting a subset of our graph of possibilities. You  can kind of imagine a GraphQL query being a way to craft your own  custom endpoint, but one that doesn't cause the burden on API developers  as creating an actual custom endpoint would. That’s because the GraphQL schema exposes the “graph” of capabilities, but does not care how it will be used precisely. This of course is a design decision that makes a tradeoff. A GraphQL API will rarely be as optimized as an endpoint answering the need of a specific client. However, this was a conscious decision, to allow our API to handle  different use cases while keeping a sane development experience.

What  this property of GraphQL gives us is that we can provide as many ways  of enabling use cases we want in a GraphQL schema. Clients can then select what they require, but don’t need to pay the cost of supporting other use cases. Even the server developers don’t need to pay the cost because of how a GraphQL schema if usually developed, where each field or use case is implemented in isolation. Of  course, the power we give to clients comes with great responsibility.  Often times this will lead to an experience that requires a bit more  work to get what they want, and discover how to consume their use cases. We’ll talk about this tradeoff, and how we can help with this in Chapter X: Documenting a GraphQL API.

Think  about Netflix’s solution and Soundcloud’s solution. When we compare  them to what GraphQL offers, even if the final solution looks quite  different, they can be quite similar. GraphQL’s “engine”, the algorithm that can execute a client’s query against a GraphQL API Schema is in  fact kind of our own powerful, yet complex “Client specific server adapter”. A GraphQL API can act as these “BFFs”,  but within just one service, because of how decoupled clients can be  from how the schema evolves, and how schema developers can add  functionality without worrying about adding weight to existing ones.

By  having this context in mind, we can make better decisions when it comes  to designing a great GraphQL API. We will frequently go back to GraphQL’s raison d’être as we look for the best practices along the  book. As you can see, GraphQL was born in a very specific context to  solve a particular problem. It is an excellent way to write APIs, but  other ways exist as well, as we explored in this chapter. This book is  not here to convince you to use GraphQL for everything, but rather to  teach you how to do it correctly if you do choose to use it. Enjoy!


Enjoyed the post? Subscribe to Production Ready GraphQL!