Introduction to GraphQL

In this article, we'll take a look at GraphQL and go through a basic example while exploring why it's a great supplement for REST APIs.

The history of GraphQL

GraphQL is a data query and manipulation language created by Facebook to solve specific problems when it comes to querying complex data. Why Facebook? They had a perfect use case for such a query language. Consider an example where you want to see a post by a specific user and all the likes and comments for this post - where each like and comment should at a bare minimum display that user's avatar and name.

Having such a query in place is complex which often will also mean that it will be slow to respond, so the solution is of course to create two separate REST API endpoints. Now, this is simple (well, not really) for this situation but imagine multiple data sources with more complex requirements - this can quickly escalate into a nightmare situation from a developer's perspective.

What is the solution? GraphQL - it introduces a single endpoint that can take complex queries and return the data in the format that we request it - this means that data can be retrieved with relative ease from endpoints.

Straightforwardly, we can think of GraphQL as a proxy between our application and a REST API.

Example

In our example, we will have a very simple dataset - a list of people each having a name, and an age property as well as a friends property which will be an array of IDs:

const data = [
  { id: 0, name: 'Tamas', age: 30, friends: [1] },
  { id: 1, name: 'Susan', age: 45, friends: [0, 2] },
  { id: 2, name: 'Joe', age: 24, friends: [] }
];

The next thing that we need to do is to build a GraphQL Schema. This schema defines what objects our queries will return, along with the datatypes as well. Essentially GraphQL services define a set of types that define the set of data on which we can query. When the query comes in it is validated and executed against the specified schema.

One of the most basic GraphQL schemas is an object type that represents an object that we can fetch from the service.

To use a schema, we will be using the graphql npm module which is installable via npm i graphql.

const { buildSchema } = require('graphql');
const schema = buildSchema(`
  type Query {
    people: [Person]
    person(id: Int): Person
  }

  type Person {
    id: Int,
    name: String,
    age: Int
    friends: [Person]
  }
`);

As you can see from the above, we created a Person type as well as a Query type. Remember this: every GraphQL service needs a query type. (It can also have a mutation type, but that's optional). This query type is special because they define the entry point of every GraphQL query.

So simply put, above we have specified a Person type - and this type has properties - an id, name, age and friends - each having a data type on its own right and the friends property is an array of the Person type - achieving recurrence.

Note that arguments for GraphQL types can be required or optional. Specifying a type with an exclamation mark indicates that it's a non-nullable value, e.g.: name: String!

Let's talk about the Query type as well for a moment. This type specifies two values - people that returns an array of the Person type and person(id: int) that returns a single person. The id is a changeable parameter.

The next step is to build up the what the query returns. To achieve this, we'll be using Restify and a middleware (created for ExpressJS but in this article, I am showing that it can be used with other REST APIs as well as a middleware). Make sure that the dependencies are installed: npm i restify express-graphql.

const restify = require('restify');
const graphqlHTTP = require('express-graphql');

const server = restify.createServer();
server.post('/graphql', graphqlHTTP({
  schema,
  rootValue: root,
  graphiql: false
}));

server.get('/graphql', graphqlHTTP({
  schema,
  rootValue: root,
  graphiql: true
}));

server.listen(3000, () => console.info('Server is up'));

In the above code, we do not see anything special with regards to the basic setup of Restify.

However, notice the two routes - GET and POST. These are the routes that are using the middleware, and they also require the schema. That is the schema that we have specified earlier. The rootValue property specifies the root node for our graph - this is something that we'll define in just a minute.

Let's also spend a few moments on discussing the graphiql property. This property is set to be true for the GET method. This property enables a visual interface to write GraphQL queries - so if we navigate to http://localhost:3000/graphql in our browser, we should see the visual explorer.

This tool not only allows us to construct queries but it also helps with exploring the data types that we have - just press ctrl - space which will display all the available types.

Please note that tools such as Insomnia also have support for making GraphQL queries natively within the UI.

The last thing that's missing is the root - the actual graph. In other words, what data should we return if someone queries for say a person? Think about it this way: for every property that we have specified in the Query type we need to write some code to retrieve the actual data. GraphQL refers to this as a resolver. One key point to make here is that the API and the database are now decoupled - this means that we can execute queries against our dataset that may not be specified with the same name in the database. (The database could have a field called fullName while in the GraphQL schema we could have a field called name only and query using this, against the fullName)

Let's take a look at the most obvious one, people. If we query for people we need to return the entire dataset:

const root = {
  people: () => {
    const data = require('./data');
    return data;
  },

Okay so now we have the graph, we have the types, it's time to write some queries. Fire up the GraphiQL interface (port 3000 in the browser) and let's retrieve the name of all the people. The query looks like this:

{
  people {
    name
  }
}

And that's it. If we also want to retrieve the age we need to add another property:

{
  people {
    name,
    age
  }
}

Retrieving a person is not going to be more complicated at all; however, there's some extra work that we need to do to list the friends for a person. Essentially we need to find the person by the friend ID and append their details to our dataset:

  person: ({ id }) => {
    delete require.cache[require.resolve('./data')]
    const data = require('./data');
    const friendIds = data[id].friends;
    if (friendIds.length === 1) {
      const friendObject = data.find(obj => obj.id === friendIds[0]);
      delete friendObject.friends;
      data[id].friends = [ friendObject ];
    } else if (friendIds.length > 1) {
      const friends = friendIds.map(friendId => {
        return data.find(obj => obj.id === friendId);
      });
      friends.forEach(friend => delete friend.friends);
      data[id].friends = friends;
    }
    return data[id];
  }

Please note that because of working with static data there are some quirks that the code has to overcome and that's why we see a lot of 'hacks' in place - like the removal of the required module from the cache. This is only required for the purposes of this example.

Let's now run a query to return a person's name, and the name of his/her friends:

We now have a working GraphQL server. It's also very easy to consume this GraphQL server and their queries from a frontend - Apollo, for example, has clients for the most popular libraries.

I hope this article was helpful in showing the basics of GraphQL - and remember, GraphQL is not going to replace REST APIs, but it supplements it. If you do not have any issues with your current REST API design, that's great. But if you're scratching the limits of what you can achieve with the API, maybe it's time that you give GraphQL a go!