GraphQL Basics


GraphQL Basics

GraphQL is a query language specification created by Facebook as an alternative to RESTful end-points. Servers can implement it in any programming language. You can check out Facebook’s javascript implementation of a GraphQL server or other language implementations already available.

GraphQL Logo

Some history for context

In 2012 Facebook’s notion that the mobile apps would be mainly used only when people were away from a desktop computer quickly faded and its increased use shifted the company’s attention from web to mobile.

At that time, they were heavily relying on their RESTful API which was proving not to be the best technology choice for the adverse network conditions that mobile devices have to deal with. Parallel requests and secondary requests for supplementary data were causing huge problems, especially in terms of response time. One solution was to design many custom resources to allow for single round trips. But those created new scalability and maintenance difficulties caused by the high coupling and low cohesion between the client apps and data servers.

In the search for a better strategy, Facebook engineers figured that product developers shouldn’t be thinking about data with a sequel or RESTful-like mind set. Or in other words, best principles for storage and retrieval of data should not be their main concern. Instead, they should be thinking in terms of the network of data and the relationships between all the pieces of information.

In that context, GraphQL was born.

How does it work?

A GraphQL document has one or more operations, similar to resources in an RESTful API. Operations can be of two types: Query or Mutation.

Let’s see our first example of a GraphQL query operation.

query_1

Although your first impression might be that that’s JSON, it is not. As we mentioned before, GraphQL was designed with the client in mind. GraphQL designers wanted to be able to write a query that would be similar to the expected returned data schema.

Notice that our query above has three distinct areas.

  • client is the query operation;
  • (id: 1) contains arguments that we want to be passed to the query operation;
  • The selection set with the id and name fields, which are the ones that we would like the query to return.

Let’s see how the server would respond to such a query.

{
  "data": {
    "client": {
      "id": "1",
      "name": "Neil Peart"
    }
  }
}

As expected, we received a JSON document with a schema that is very similar to our query.

Let’s try another one, this time using another query operation.

query_2

This time we are using the products query operation and passing two arguments to it: one for filtering by product_category_id and an instruction to return the data by descending order of the field price. Also, notice how our selection set now contains a different set of fields (name, shell_size, manufacturer and price).

You now can probably guess how the returned data will look like.

{
  "data": {
    "products": [
      {
        "name": "Mapex Black Panther Velvetone 5-pc Drum Shell Kit",
        "shell_size": "22\"x18\" Bass Drum, 10\"x8\" & 12\"x9\" Toms, 14\"x14\" & 16\"x16\" Floor Toms",
        "manufacturer": "Mapex",
        "price": 2949.09
      },
      {
        "name": "Pearl MCX Masters Natural Birdseye Maple 4pc Shell Pack with 22\" Kick",
        "shell_size": "22x18\" Virgin Bass Drum 10x8\" Rack Tom 12x9\" Rack Tom 16x16\" Floor Tom",
        "manufacturer": "Pearl",
        "price": 1768.33
      }
    ]
  }
}

As you can see with these initial examples, GraphQL allows the client to specify exactly what it needs, avoiding over‐fetching and under‐fetching of data. Contrast this with RESTful APIs where you might have multiple resources for each specific client need, or one resource that will try to supply information for multiple clients at the same time.

This is a good time to mention again that GraphQL is just a query language specification. All operations, arguments that may be passed, and all available fields that may be used in the selection set have to be defined and implemented by your GraphQL server.

So now, let’s see how GraphQL solves another issue. Say we need to fetch our product_categories and each of their related products. On a RESTful resource you could implement an end-point capable of fetching all data with one request, but in most cases, you have a resource that will need you to first request your product_categories and then, in one or more requests, fetch its associated products.

Let’s see how we would do this using GraphQL.

query_3

We used our product_categories query operation with no arguments this time. In our selection set we are specifying that we want the name of each product_category and also, just like a field, we ask for all its child products. To do this nested query, noticed how we also had to specify which product fields we want the server to return. Let’s see our returned data.

{
  "data": {
    "product_categories": [
      {
        "name": "Acoustic Drums",
        "products": [
          {
            "name": "Mapex Black Panther Velvetone 5-pc Drum Shell Kit",
            "price": 2949.09
          },
          {
            "name": "Pearl MCX Masters Natural Birdseye Maple 4pc Shell Pack with 22\" Kick",
            "price": 1768.33
          }
        ]
      },
      {
        "name": "Cymbals",
        "products": [
          {
            "name": "Sabian 18\" HHX Evolution Crash Cymbal - Brilliant",
            "price": 319
          },
          {
            "name": "Zildjian 20\" K Custom Dry Light Ride Cymbal",
            "price": 396.99
          },
          {
            "name": "Zildjian 13\" K Custom Dark Hi Hat Cymbals",
            "price": 414.95
          }
        ]
      }
    ]
  }
}

There is no limit to the number of levels of nesting you can do, it all depends on how your GraphQL data schema and server implements it. For example, the query below is perfectly valid.

query_4

Here we are asking the server to return purchases for a specific client. Our selection set specifies not only fields from purchase, but also fields from its related product and the name of its product_category.

One of the principles that drive GraphQL specification is strong-typing.

Every GraphQL server defines an application‐specific type system. Queries are executed within the context of that type system.

That means that your selection set can query fields of Scalar type which are primitive types like Int, Float, String, Boolean and ID.

In our last example, along with the purchase fields, we queried product, client and product_category, and guess what, these are user defined type fields, or, according to the specification, ObjectTypes.

Since GraphQL queries are hierarchical, composed and representing information in a tree-like schema, think of Scalar types as leafs and Object types as branches. To learn more about the type system check out the GraphQL reference.

Operation and Field Aliasing

In a GraphQL query you can also specify alias names for operations or fields. In our example, the server allows a client to fetch information for the cymbal_size of a product. But imagine that your client implementation needs the name of that field to be diameter instead.

GraphQL also uses the operation name as the returned objects name, so if the client wants they can also define an alias for it.

Let’s see how that would work.

query_5

And the returned data.

{
  "data": {
    "my_product": {
      "id": "3",
      "name": "Zildjian 13\" K Custom Dark Hi Hat Cymbals",
      "diameter": "13\""
    }
  }
}

Fragments

Now the client app from our example will need to fetch separate lists for drum sets and cymbals. With GraphQL you are not limited by only one operation at a time and, just like with fields, we can also specify an alias for the returned objects.

query_6

Notice how we are using this opportunity to also fetch specific fields for each category: pieces, shell_size, shell_type for drumsets, and cymbal_size for cymbals.

{
  "data": {
    "drumsets": [
      {
        "id": "2",
        "name": "Mapex Black Panther Velvetone 5-pc Drum Shell Kit",
        "manufacturer": "Mapex",
        "price": 2949.09,
        "pieces": "5",
        "shell_size": "22\"x18\" Bass Drum, 10\"x8\" & 12\"x9\" Toms, 14\"x14\" & 16\"x16\" Floor Toms",
        "shell_type": "9ply, 8.1mm Maple/Walnut/Maple"
      },
      {
        "id": "1",
        "name": "Pearl MCX Masters Natural Birdseye Maple 4pc Shell Pack with 22\" Kick",
        "manufacturer": "Pearl",
        "price": 1768.33,
        "pieces": "4",
        "shell_size": "22x18\" Virgin Bass Drum 10x8\" Rack Tom 12x9\" Rack Tom 16x16\" Floor Tom",
        "shell_type": "Maple"
      }
    ],
    "cymbals": [
      {
        "id": "5",
        "name": "Sabian 18\" HHX Evolution Crash Cymbal - Brilliant",
        "manufacturer": "Sabian",
        "price": 319,
        "cymbal_size": "18\""
      },
      {
        "id": "3",
        "name": "Zildjian 13\" K Custom Dark Hi Hat Cymbals",
        "manufacturer": "Zildjian",
        "price": 414.95,
        "cymbal_size": "13\""
      },
      {
        "id": "4",
        "name": "Zildjian 20\" K Custom Dry Light Ride Cymbal",
        "manufacturer": "Zildjian",
        "price": 396.99,
        "cymbal_size": "20\""
      }
    ]
  }
}

We are specifying different fields for each operation, but, unfortunately, we are also repeating common fields (id,name, manufacturer and price).

To avoid this kind of repetition, GraphQL allows us to use Fragments. Let’s go ahead and extract those repeating fields to a Fragment.

query_7

To consume a Fragment in our operation we use the spread operator(…).

Variables

While we are trying to reduce repetitions on our query statements, let’s imagine another situation.

query_8

We are querying the server using two operations and each of them is using the client_id argument. It would be great to have a way of centralizing that information. For that, in GraphQL we can utilize variables. Let’s modify our example using a clientID variable.

query_9

We declared our variables on top of our operation and we’ll be able to use them throughout the execution scope of the operation. Along with the variable definition, we need to provide a JSON with our variable values.

{
  "clientId": 1
}

We could also specify a default value for a variable.

query_10

Here, in case no date value is passed, we’ll use the default value.

Mutations

GraphQL is not limited to only retrieving data, servers can also implement operations for creating, updating and destroying data. For these types of operations GraphQL uses the term Mutation. Let’s see some examples of how these work. We will start by creating a new client.

query_11

We now specify the type of operation to mutation instead of query. For the create_client operation, we passed the arguments we wanted the new record to be saved with and, finally, our selection set is defining which fields we want to return after the record is created.

{
  "data": {
    "create_client": {
      "id": "5",
      "name": "John Bonham",
      "dob": "2016-05-31"
    }
  }
}

We created this record with a tiny mistake. Notice that the year of birth is wrong. Let’s fix that using the update_client operation.

query_12

As before, the response is the updated record with the fields we specified.

 {
   "data": {
     "update_client": {
       "id": "5",
       "name": "John Bonham",
       "dob": "1948-05-31"
     }
   }
 }

And finally, if we wanted to destroy it:

query_13

Remember, create_client, update_client and destroy_client are operations defined by our implementation of the GraphQL server. Wouldn’t it be great if we had a way of find out which operations a specific server implements? GraphQL helps us as well with introspection.

Introspection

A great feature of GraphQL is the ability to query its own schema, allowing you to view many of its details like which query and mutation operations are available, which arguments they accept and even which available fields we can query.

Let’s query our server and fetch all query operations available.

query_14

This will return all query operations available to us.

{
  "data": {
    "__schema": {
      "queryType": {
        "name": "Query",
        "fields": [
          {
            "name": "client"
          },
          {
            "name": "clients"
          },
          {
            "name": "product"
          },
          {
            "name": "product_categories"
          },
          {
            "name": "product_category"
          },
          {
            "name": "products"
          }
        ]
      }
    }
  }
}

Same thing with mutation operations.

query_15

For our example implementation, you see that we only have 3 mutation operations.

{
  "data": {
    "__schema": {
      "mutationType": {
        "name": "Mutation",
        "fields": [
          {
            "name": "create_client"
          },
          {
            "name": "destroy_client"
          },
          {
            "name": "update_client"
          }
        ]
      }
    }
  }
}

And just like the other queries we ran, you can fetch more information. For instance, let’s fetch the name of the arguments available for query operations.

query_16

For the sake of brevity, we are only showing two results.

{
  "data": {
    "__schema": {
      "queryType": {
        "name": "Query",
        "fields": [
          {
            "name": "clients",
            "args": [
              {
                "name": "ids"
              },
              {
                "name": "name"
              },
              {
                "name": "dob"
              }
            ]
          },
          {
            ...
          },
          {
            "name": "products",
            "args": [
              {
                "name": "ids"
              },
              {
                "name": "product_category_id"
              },
              {
                "name": "order"
              },
              {
                "name": "limit"
              }
            ]
          }
        ]
      }
    }
  }
}

You see that this server implements a clients query operation, which can receive arguments such as ids, name and dob. The second operation show, allows us to query products and, in this one, the server receives arguments for ids, product_category_id, order and limit.

Conclusion

GraphQL gives us many new possibilities for creating more resourceful end points for client applications. If you are interested in learning more about it, I really encourage you to read the specification and try to implement a small server.

In future posts we will talk about some other more advanced features of GraphQL such as interfaces, unions and directives. We are also going to walk you through a simple GraphQL server implementation.

References:

Let's stay connected. Join our monthly newsletter to receive updates on events, training, and helpful articles from our team.