Blogpost

The ins and outs of GraphQL: Syntax

The term GraphQL is getting a lot of attention lately. As its name suggests, it’s a query language, just like SQL (Structured Query Language). Unlike SQL however, it’s used not in database interactions, but in web APIs. To explain the benefits it can bring there, we’ll explore GraphQL in a series of 3 blog posts, of which this one is the second:

  1. Context: in the blog post we’ll put GraphQL in its historical perspective and wider context, and will discuss its general pros and cons
  2. Syntax: this blog post will offer an introduction and general overview of the GraphQL syntax and how it’s used to collect data by the consumers of your API
  3. With Java and Spring: we’ll discuss 2 prominent Java libraries that enable you to create a GraphQL server API. The first relies on plain Java while the second build on the Spring framework

Graphql

Schema

A GraphQL API consists of a single endpoint, with a single schema that defines the data that is available to be queried. For the code examples below, we’ll start of with a hypothetical GraphQL API with the following schema.

				
					type Query {
   students: [Person]
   student(id:ID!): Person
}
type Person {
   id:ID!
   firstName: String
   lastName: String
   fullName: String,
   dateOfBirth: Date,
   picture: Url,
   address: Address
}
type Address {
  city: String
  street: String
  postalCode: String
  zipCode: String @deprecated(reason: "Use ’postalCode’.")
}
scalar Date
scalar Url
				
			

Let’s start by unpacking the first part of that:

  • Type Query represents the root of your (query) API
  • In our example, the root has two sub-objects, i.e. students (which is a list of Person) and student (which is a single person)
  • The student object requires an id argument of the type ID, and it’s required (hence the exclamation mark)

 

You’ll already have noticed that a GraphQL schema is strongly typed, including the types Person and ID. Of those two, ID is a scalar (a primitive leaf value) just like String, Int, Float, and Boolean. These scalars are built-in, but an API can also define additional custom scalars, such as Date and Url in the example above. These two are of course returned as simple string values, but they have the guarantee from the API that they will always have the correct format (e.g. ISO-8601 standard date format and a valid URL, respectively).

The Person and Address types are custom types, which are composed of scalars and/or other custom types. In the Address definition, you’ll notice the @deprecated, which includes a reason for the field being deprecated.

There’s a bit of complexity in such a schema. However, a major benefit of GraphQL is that this schema serves as a contract and simultaneously as concrete documentation, which for most APIs is either missing or requires a substantial amount of effort to create and to maintain.

 

Queries

Based on the schema we’ve defined above, we can retrieve data from the GraphQL API via queries. These contain a description of the information that you want to retrieve, including object (or objects) and the fields that you are interested in. Just like the statically typed schema, a query is also very explicit and concrete. Take for instance the query:

				
					{
  students {
    fullName
  }
}
				
			

This is probably the simplest kind of query you can perform with GraphQL and it will return all the students and for each student will include their fullName – nothing more and nothing less. Note that the data is returned in a JSON format, and that the data itself is always placed under a data field, which leaves room for other top-level response fields (e.g. meta or errors).

				
					{
  students {
    id,
    dateOfBirth,
    address {
      city
    }
  }
}
				
			

As you’d expect, that would return something like:

				
					{
   "data": {
      "students": [
         {
           "id": 1,
           "dateOfBirth": "2000-05-02",
           "address": {
             city: "London"
           }
         },
         {
           "id": 2,
           "dateOfBirth": "2002-07-11",
           "address": {
             city: "Paris"
           }
         },
      ]
   }
}
				
			

Keep in mind we’ve been querying the students object, which contains the full list of all students and will always return a list of students. In our example, if you want to fetch only a single student, you need to rely on a different object (student), and pass it argument. 

				
					{
  student(id: 2) {
    firstName
    lastName
  }
}
				
			

Just to illustrate that it’s possible, instead of asking for the fullName, we’re now requesting the firstName and lastName. This query will return:

				
					{"data":{"student":{"firstName":"Jane","lastName":"Doe"}}}
				
			

Finally, it’s important to note that if you for instance need to do both of the two last queries (i.e. students and student(id: 2)), then there is no need to perform separate API calls. You can simply use:

				
					{
  students {
    id,
    dateOfBirth,
    address {
      city
    }
  }
  student(id: 2) {
    firstName
    lastName
  }
}
				
			

and this will supply you with:

				
					{
  "data": {
    "students": [
       {
         "id": 1,
         "dateOfBirth": "2000-05-02",
         "address": {
           city: "London"
         }
       },
       {
         "id": 2,
         "dateOfBirth": "2002-07-11",
         "address": {
           city: "Paris"
         }
       },
    ],
    "student": {
      "firstName": "Jane",
      "lastName": "Doe"
    }
  }
}
				
			

Mutations

So far we’ve only retrieved information with the API. Usually though, it’s also necessary that the end-user can push or modify data. In GraphQL this is done via mutations. To continue with our example, we’ll add the following to our schema:

				
					type Mutation    
  createStudent(firstName:String, lastName:String, dateOfBirth, Date): Person
}
				
			

This createStudent mutation allows us to pass a firstname, lastname and date of birth, and will return a Person. Just as in a regular query, we can specify the information we want to have returned. For example, we can simply ask for the id.

				
					{
  createStudent(firstName: ”Jack”, lastName: “Black”, dateOfBirth: “1969-09-28”) {
    id
  }
}
				
			

That will return:

				
					{"data":{"createStudent":{"id":3}}}
				
			

Given that this equates to a method call with input parameters and an output value, the complexity of the query and the effect that it can have is entirely up to the API developer to decide. As such, we’re still left with finding the balance between a single mutation that can do many things versus several mutations that have a single, focused concern.

 

Conclusion

Since the GraphQL syntax is quite expansive (as you’ll notice from the online specs), we’ve only managed to go over part of it here. That part should however suffice to already accomplish plenty with GraphQL and allow you to leverage much of the benefit that GraphQL can provide. For that, you’ll of course need to have a server application that provides a GraphQL API, and that’s what we’ll cover in the next and last blog post of this series.

Portretten Continuum 32 kopie scaled 1 q2ymoeodo52q4i1olezivue73ta7nnoyn5gqz8529k

Alain Van Hout

Java Software Crafter

Alain Van Hout is a Java Software Crafter with a Master in Science (Biology) and experience in academic research concerning evolutionary biology. Currently, Alain is working on web applications and API integrations at the genomics data analysis provider BlueBee (Mechelen), where he combines his background in biology with his passion for software Craftsmanship.