Bram Adams Bramblings of a Madman

GraphQL -- the hero we need but don't deserve

Note: This post will aready assume that readers know the basics of GraphQL. If you don’t know GraphQL or would like a refresher, I recommend the fantastic tutorial on the Apollo website.

Summary:

  • GraphQL provides a ton of side benefits beyond its main functionality as a query language for your API
    • It allows developers to understand their data model better
    • Keeping up with changes from external APIs becomes trivial
    • Since GraphQL is agnostic, you can throw it literally anywhere into a pipeline and it will make life easier
    • Clients relying on you will be impressed by the amount of ground your API covers
  • GraphQL makes it difficult to hide implementation details from clients
    • This problem can be minimized by adding another layer of abstraction on your client layer

It’s time for REST to take a rest

GraphQL is great. It helps solve a lot of problems that plague developers today and make the development more arduous for everyone involved. I don’t believe that GraphQL is a complete replacement for REST. However, when working together, GraphQL and REST can make developer and client lives easier.

Imagine that we run a food wiki. Our wiki uses a lot of external data to load in calories, nutrition info, etc. It also has a UI that allows users to choose what is important to them about their food and compare foods against each other. Basically, you can think of our food wiki like a personalized menu. Users who are on diets can search for foods with low fat or carb content. Users who don’t like certain foods can find alternative ones.

When building an app like this, the first thing we notice is that our service has a ton of use cases. Following best REST API practices, we can at most get two collections working in tandem. Our developers will quickly be swallowed by the use cases. How can we predict that a user will want to only look at the Vitamin C content of two fruits, and nothing else? If we use a regular REST API to consume from our data sources, we will have to add logic to filter out the fields we want in the front end. This might look something like:

GET /foodInfo/orange?fields=Vitamin_C

// Response from external API www.somefruitwebsite.com/fruit/orange
// we then use our query param 'Vitamin C' to filter out the fields we care about
const response = {
    "fruit" : "Orange",
    "calories" : 100,
    "nutrition" : {
        "Vitamin_C" : "500mg",
        "Protein" : "0g",
        "Sugar" : "14g"
    }
}; 

// ES6 import to get pick from lodash
import { 'pick' } from 'lodash';

// return only the vitamin C field based on user query
const returnSubset = (original, args = []) => pick(original, args); 

returnSubset(response, ['nutrition.Vitamin_C']);

With GraphQL, this problem becomes much less annoying to solve. By leveraging the schema (the representation of our data model) and our resolver (effectively fetching from different places), we can let GraphQL do the heavy lifting of finding and assigning the fields that we care about. Due to this, we can intercept any[1] query a user might want to make. It’s just a graph after all!

Consumption to Production

GraphQL isn’t only great for your clients however. Due to the nature of resolvers, we can achieve a deeper relatinship with our data model. Resolvers are just functions. They don’t care which backend they point to, their only contract is with your schema. This allows developers to knock out two birds with one stone while implementing the resolvers. They’ll be able to understand the fields they get back from a database or an API as opposed to just using it as a proxy. For example, let’s say you have an object in this shape:

{
    "id" : "some_id123",
    "food" : "Papaya",
    "statistics" : {
        "average_weight" : "180g",
        "calories" : "67"
    },
    "taste_rating" : 7
}

Let’s say I want to calculate how much a user might enjoy a food ruit based off of calories and their personal “taste index”. Using resolvers, we can use our context parameter to assign a computation based on the user. We can use our arguments to fetch only the fields that pertain to a user (id and such), and we can use our obj to fetch trivial information (this is generally used when you already are building off of a skeleton. It also really comes in handy if you have a list of ids you want to resolve!) This is all included without putting any overhead onto the developer. You don’t have to think about field coercion, what types things have to be in, how to build a response that’s testable, etc. You can just focus on the implementation! It feels really good to have resolvers sometimes be as simple as these:

1)

Food : {
    // this should still do error checking of course ;) not every result is a 200
    calories: (obj, args, context,) => axios.get('some_url_with_calorie_info/' + args.id).then(response.data.statistics.calories) 

    // this will return
    {
        "data" : {
            "foodName" : "Papya",
            "calories" : 67
        }
    } 
}

However if we want our resolver to just trivially place the data in our result without mutating it, we can use our schema to do that too.

2)

Food : {
    // this should still do error checking of course ;) not every result is a 200
    // no more manual path search! 
    calories: (obj, args, context,) => axios.get('some_url_with_calorie_info/' + args.id).then(response.data) 

    // this will return
    {
        "data" : {
            "food" : "Papya",
            "statistics" : {
                "calories" : 67
            }
        }
    } 
}

Because GraphQL waits for promises to resolve, we don’t even need to worry about keeping track of all the async code we’re running! NB: It also works with Promise.all() and async/await!

GraphQL will automagically pick out the fields you’ve specified in your query to return as well! And the best part is, if they change it to the shape below, we have options with how to deal with it!

We can:

  1. If we are only interested in the one field as we are above, we can just change the path (#1 above)
  2. Go into the schema and change the data model (#2 above)
{
    "id" : "some_id123",
    "food" : "Papaya",
    "statistics" : {
        "average_weight" : "180g",
        "kcals" : "67" // changed key name from calories to kcals
    },
    "taste_rating" : 7
}

GraphQL the Honey Badger

GraphQL don’t care. GraphQL don’t give a shit.

Because of how agnostic a GraphQL server is, we can also rethink microservices. We can start reframing the architecture diagram that we’re so used to seeing. IBM has a fantastic article on this that I highly reccomend. But the idea basically is that we’re used to the idea of exposing an API for every microservice that has it’s own versions and complexities. When you add a GraphQL server layer on top of all of these APIs, teams don’t need to update their code every time a microservice they talk to updates. Simply edit the GraphQL server schema (using schema stitching) and downstream services won’t notice a thing! Everyone will just hit the /graphql endpoint anyway!

And More?!

This article just scratches the surface of why you should want to learn GraphQL. It doesn’t even address mutations, fragments, subscriptions, the info field of the resolver function, conditionals, directives and so on. GraphQL (while annoying to learn at first) is an invaluable tool that can fit anywhere on a pipeline. I highly reccomend giving it a go!

[1] not really though. If a user is interested in some query where they want a dump of all the fields, it’s a hassle to build that query with GraphQL. Fragments can help but they don’t solve everything.