Introduction to GraphQL

Tags: GraphQL

I was preparing for a presentation to my team at work on GraphQL and thought that it would be good to share the material on my blog. My two primary sources for learning GraphQL have been the GraphQL.org site and this course on PluralSight Building Scalable APIs with GraphQL by Samer Buna. I include a lot of related links at the end of the article that contain some great information.

I prepared the GraphQL examples to be executed against the GraphCMS SWAPI API. The REST examples were prepared to be run against the SWAPI API.

What is GraphQL?

As defined on the GraphQL.org site

GraphQL is a query language for your API, and a server-side runtime for executing queries by using a type system you define for your data. GraphQL isn’t tied to any specific database or storage engine and is instead backed by your existing code and data.

Why might you choose to build/consume a GraphQL Api over a RESTful one?

The best list of reasons I have found to date is from Derek Haynes in his article An Introduction to GraphQL via the GitHub API

  • Get exactly what you ask for: Your queries mirror the shape of data returned from a GraphQL API, so there’s no confusion over which fields an API call returns.
  • Nesting: Graph queries can be nested, so you can fetch data across relationships. Without GraphQL, gathering data across relationships typically involves multiple, slower, HTTP calls.
  • Strongly typed: It’s important to get the data format you expect when working with an external data source. GraphQL is strongly typed, so you get these guarantees.
  • Introspective: Since a GraphQL server can be queried for the calls it supports, you can use tools like GraphiQL to organically explore versus hunting through API docs.
  • No versioning: Since you get what ask for, fields won’t be added or removed out from under you. When fields are deprecated, they can be marked as such to provide advance warning.

Get only what you need and no more

Our first example is very simple all I want back is the person’s name. Here is what that query looks like against the GraphQL API.

{
  Person(id: "cj0nv9p8yewci0130wjy4o5fa") {
    name
  }
}

Here is what is passed over the wire to the server

{"query":"{\n  Person(id: \"cj0nv9p8yewci0130wjy4o5fa\") {\n    name\n  }\n}\n","variables":null,"operationName":null}

Here is what is returned from the server, exactly what we asked for and only the data we need.

{
  "data": {
    "Person": {
      "name": "Luke Skywalker"
    }
  }
}

Now we are going to call the REST based SWAPI API to get the name. Remember all we really need is the name.

http://swapi.co/api/people/1/

This is what is returned.

{
    "name": "Luke Skywalker", 
    "height": "172", 
    "mass": "77", 
    "hair_color": "blond", 
    "skin_color": "fair", 
    "eye_color": "blue", 
    "birth_year": "19BBY", 
    "gender": "male", 
    "homeworld": "http://swapi.co/api/planets/1/", 
    "films": [
        "http://swapi.co/api/films/6/", 
        "http://swapi.co/api/films/3/", 
        "http://swapi.co/api/films/2/", 
        "http://swapi.co/api/films/1/", 
        "http://swapi.co/api/films/7/"
    ], 
    "species": [
        "http://swapi.co/api/species/1/"
    ], 
    "vehicles": [
        "http://swapi.co/api/vehicles/14/", 
        "http://swapi.co/api/vehicles/30/"
    ], 
    "starships": [
        "http://swapi.co/api/starships/12/", 
        "http://swapi.co/api/starships/22/"
    ], 
    "created": "2014-12-09T13:50:51.644000Z", 
    "edited": "2014-12-20T21:17:56.891000Z", 
    "url": "http://swapi.co/api/people/1/"
}

What if I want to display the title of the films Luke Skywalker is in?

Lets get the information using the REST Api. It ends of taking 6 REST calls to get the information and 14.7 KB of data.

http://swapi.co/api/people/1/

http://swapi.co/api/films/1/

http://swapi.co/api/films/2/

http://swapi.co/api/films/3/

http://swapi.co/api/films/6/

http://swapi.co/api/films/7/

Here is an example of what is returned when calling the films API.

{
    "title": "A New Hope",
    "episode_id": 4,
    "opening_crawl": "It is a period of civil war.\r\nRebel spaceships, striking\r\nfrom a hidden base, have won\r\ntheir first victory against\r\nthe evil Galactic Empire.\r\n\r\nDuring the battle, Rebel\r\nspies managed to steal secret\r\nplans to the Empire's\r\nultimate weapon, the DEATH\r\nSTAR, an armored space\r\nstation with enough power\r\nto destroy an entire planet.\r\n\r\nPursued by the Empire's\r\nsinister agents, Princess\r\nLeia races home aboard her\r\nstarship, custodian of the\r\nstolen plans that can save her\r\npeople and restore\r\nfreedom to the galaxy....",
    "director": "George Lucas",
    "producer": "Gary Kurtz, Rick McCallum",
    "release_date": "1977-05-25",
    "characters": [
        "http://swapi.co/api/people/1/",
        "http://swapi.co/api/people/2/",
        "http://swapi.co/api/people/3/",
        "http://swapi.co/api/people/4/",
        "http://swapi.co/api/people/5/",
        "http://swapi.co/api/people/6/",
        "http://swapi.co/api/people/7/",
        "http://swapi.co/api/people/8/",
        "http://swapi.co/api/people/9/",
        "http://swapi.co/api/people/10/",
        "http://swapi.co/api/people/12/",
        "http://swapi.co/api/people/13/",
        "http://swapi.co/api/people/14/",
        "http://swapi.co/api/people/15/",
        "http://swapi.co/api/people/16/",
        "http://swapi.co/api/people/18/",
        "http://swapi.co/api/people/19/",
        "http://swapi.co/api/people/81/"
    ],
    "planets": [
        "http://swapi.co/api/planets/2/",
        "http://swapi.co/api/planets/3/",
        "http://swapi.co/api/planets/1/"
    ],
    "starships": [
        "http://swapi.co/api/starships/2/",
        "http://swapi.co/api/starships/3/",
        "http://swapi.co/api/starships/5/",
        "http://swapi.co/api/starships/9/",
        "http://swapi.co/api/starships/10/",
        "http://swapi.co/api/starships/11/",
        "http://swapi.co/api/starships/12/",
        "http://swapi.co/api/starships/13/"
    ],
    "vehicles": [
        "http://swapi.co/api/vehicles/4/",
        "http://swapi.co/api/vehicles/6/",
        "http://swapi.co/api/vehicles/7/",
        "http://swapi.co/api/vehicles/8/"
    ],
    "species": [
        "http://swapi.co/api/species/5/",
        "http://swapi.co/api/species/3/",
        "http://swapi.co/api/species/2/",
        "http://swapi.co/api/species/1/",
        "http://swapi.co/api/species/4/"
    ],
    "created": "2014-12-10T14:23:31.880000Z",
    "edited": "2015-04-11T09:46:52.774897Z",
    "url": "http://swapi.co/api/films/1/"
}

Now lets get the data we need using GraphQL. It takes one call and only 422 bytes. That is a 34x data size difference (422 bytes vs. 14733 bytes).

Query:

{
  Person(id: "cj0nv9p8yewci0130wjy4o5fa") {
    name
    films
    {
      title
    }
  }
}

Response:

{
  "data": {
    "Person": {
      "name": "Luke Skywalker",
      "films": [
        {
          "title": "A New Hope"
        },
        {
          "title": "Revenge of the Sith"
        },
        {
          "title": "Return of the Jedi"
        },
        {
          "title": "The Empire Strikes Back"
        },
        {
          "title": "The Force Awakens"
        }
      ]
    }
  }
}

What other characters were in those movies with Luke Skywalker?

This query has two sub queries the first one to get the films Luke Skywalker plays in and another sub query for what characters are in those films. Think about the number of REST calls that would need to be made to get this same data and how much data would be transferred across the wire.

{
  Person(id: "cj0nv9p8yewci0130wjy4o5fa") {
    name
    films
    {
      title
      characters
      {
        name
      }
    }
  }
}

The GraphQL Language

Now that we have shown why you might choose to build/consume a GraphQL API over a RESTful one lets look at what components make up the GraphQL language.

Fields

At its simplest, GraphQL is about asking for specific fields on objects. In this example we are asking for the name field on the Person object.

{
  Person(id: "cj0nv9p8yewci0130wjy4o5fa") {
    name
  }
}

In this example we have added to our original request the fields films and on that object we request the field title.

{
  Person(id: "cj0nv9p8yewci0130wjy4o5fa") {
    name
    films
    {
      title
    }
  }
}

Arguments

In a system like REST, you can only pass a single set of arguments – the query parameters and URL segments in your request. But in GraphQL, every field and nested object can get its own set of arguments, making GraphQL a complete replacement for making multiple API fetches.

{
  Person(id: "cj0nv9pdlewd80130bs5vgryn") {
    name
    starships{
      id
      name
      length
    }
  }
}

In this example I want display information about Han Solo and the Millennium Falcon. I use a filter on starships to get back just the Millennium Falcon.

{
  Person(id: "cj0nv9pdlewd80130bs5vgryn") {
    name
    starships (filter: {id: "cj0nwtqpzq4tt01142nh7e9i4"}){
      id
      name
      length
    }
  }
}

Filtering is pretty flexible and powerful. For example if I wanted to show all of the really big starships say those over 100 meters:

{
  allStarships(filter: {length_gte: 100}) {
    id
    name
    length
  }
}

Alias

In a GraphQL query you can also specify alias names for operations or fields. Since the result object fields match the name of the field in the query but don’t include arguments, you can’t directly query for the same field with different arguments. That’s why you need aliases – they let you rename the result of a field to anything you want. In this example I want to know who the hero was in each the original films.

{
  newHopeHero: Person(id: "cj0nv9pdlewd80130bs5vgryn") {
    name
  }
  empireStrikesBackHero: Person(id: "cj0nv9p9wewcm01302r07xzna") {
    name
  }
  returnOfTheJediHero: Person(id: "cj0nv9p8yewci0130wjy4o5fa") {
    name
  }
}

Fragments

Now let’s say that we want to know a little more about our heros than just their name such as their homeworld and want other films they show up in. You see that our query is starting to get complicated and that it repeats itself three times. GraphQL includes fragments which let you construct sets of fields, and then include them in queries where you need to.

{
  newHopeHero: Person(id: "cj0nv9pdlewd80130bs5vgryn") {
    name
    homeworld {
      name
    }
    films {
      title
    }
  }
  empireStrikesBackHero: Person(id: "cj0nv9p9wewcm01302r07xzna") {
    name
    homeworld {
      name
    }
    films {
      title
    }
  }
  returnOfTheJediHero: Person(id: "cj0nv9p8yewci0130wjy4o5fa") {
    name
    homeworld {
      name
    }
    films {
      title
    }
  }
}

In this example we construct a heroFields fragment containing the fields we want back for each of our heros. We indicate that we want to use the fragment in our query by using the spread operator(…).

{
  newHopeHero: Person(id: "cj0nv9pdlewd80130bs5vgryn") {
    ...heroFields
  }
  empireStrikesBackHero: Person(id: "cj0nv9p9wewcm01302r07xzna") {
    ...heroFields
  }
  returnOfTheJediHero: Person(id: "cj0nv9p8yewci0130wjy4o5fa") {
    ...heroFields
  }
}

fragment heroFields on Person {
  name
  homeworld {
    name
  }
  films {
    title
  }
}

Variables

In our heros query above we are hard coding the Id for each Person query. GraphQL has support for variables which allows you to pass dynamic arguments in a separate dictionary. In the example below we create an alias for our operation HeroNameAndFilms and change the operation to take a variable of heroId. We then can use that variable anywhere we want inside of our operation. The GraphiQL interface provides a section to declare all your dynamic variables.

query HeroNameAndFilms($heroId: ID) {
  hero: Person(id: $heroId) {
    ...heroFields
  }
}

fragment heroFields on Person {
  name
  homeworld {
    name
  }
  films {
    title
  }
}
// copy this to the query variables
{
  "heroId": "cj0nv9pdlewd80130bs5vgryn"
}

So when you start working with variables, you need to do three things:

  • Replace the static value in the query with $variableName
  • Declare $variableName as one of the variables accepted by the query
  • Pass variableName: value in the separate, transport-specific (usually JSON) variables dictionary

Here is the wire format from our first query example from above where we retrieved just a Person name. You see that the object consist of a query field, variables dictionary, and an operationName.

{"query":"{\n  Person(id: \"cj0nv9p8yewci0130wjy4o5fa\") {\n    name\n  }\n}\n","variables":null,"operationName":null}

Here is the wire format of our HeroNameAndFilms operation.

{"query":"query HeroNameAndFilms($heroId: ID) {\n  hero: Person(id: $heroId) {\n    ...heroFields\n  }\n}\n\nfragment heroFields on Person {\n  name\n  homeworld {\n    name\n  }\n  films {\n    title\n  }\n}","variables":{"heroId":"cj0nv9pdlewd80130bs5vgryn"},"operationName":"HeroNameAndFilms"}

Directives

In the previous examples we used variables to avoid hard coding values in our queries. We might also need a way to dynamically change the structure and shape of our queries. Let’s make our heros operation a little more generic so that we can pass a variable to indicate whether we want the films returned in our query. We declare a new variable withFilms in our operation which is a boolean. We then update our fragment by using the @include(if: $withFilms) directive.

query Hero($heroId: ID, $withFilms: Boolean!) {
  hero: Person(id: $heroId) {
    ...heroFields
  }
}

fragment heroFields on Person {
  name
  homeworld {
    name
  }
  films @include(if: $withFilms) {
    title
  }
}
// copy this to the query variables
{
  "heroId": "cj0nv9pdlewd80130bs5vgryn",
  "withFilms": false
}

The core GraohQL specification includes two directives:

  • @include(if: Boolean) Only include this field in the result if the argument is true
  • @skip(if: Boolean) Skip this field if the argument is true

Summary

There are other parts that make up the GraphQL language, but these are the core components. I hope I have shown just how powerful GraphQL can be or at least peeked your interest to investigate it further. The best place to start is on the GraphQL.org site. Here are some other great resources:

And if you are a .NET developer here are some resources:

Related Links

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s