GraphQL Exploration

GraphQL is the new cool kid on the Block. As such, it is a young API and the documentation is still pretty PhD’esque. This post will describe my process to dig into GraphQL.

I am not that smart. Really.

I could read the GraphQL specification for hours and I would still have problems to really understand what it is about.

I need to play with concepts to really understand them.

sql-to-graphql by Espen Hovlandsdal

The Sql to Graphql is a very interesting library which takes a SQL database and generates the associated GraphQL schema.

This library comes with its own web interface to query the schema.

Another option is to use the generated schema inside a basic express node server using the Graphiql in-browser IDE provided by Facebook.

One very neat feature of sql-to-graphql is that it allows you to generate 2 kinds of schema :

This relay-style feature is very useful from an exploration perspective because relay adds its own layer of abstraction on top of a regular GraphQL schema.

The Graphql relay API provided by Facebook tries hard to hide this extra abstraction. It is then a bit tricky to detangle this extra layer by simply reading the code to create a GraphQL Relay schema.

How to experiment with GraphQL?

The process to play with GraphQL using sql-to-graphql is quite simple:

Hello world database

We can start with the simplest of all database:

mysql> desc User;
+------------+-------------+------+-----+---------+----------------+
| Field      | Type        | Null | Key | Default | Extra          |
+------------+-------------+------+-----+---------+----------------+
| id         | int(11)     | NO   | PRI | NULL    | auto_increment |
| first_name | varchar(50) | YES  |     | NULL    |                |
| last_name  | varchar(50) | YES  |     | NULL    |                |
+------------+-------------+------+-----+---------+----------------+

with the data:

mysql> select * from user;
+----+------------+-----------+
| id | first_name | last_name |
+----+------------+-----------+
|  1 | Zinedine   | Zidane    |
|  2 | David      | Beckham   |
|  3 | Cristiano  | Ronaldo   |
|  4 | Wayne      | Rooney    |
|  5 | Diego      | Maradona  |
+----+------------+-----------+

Graphql schema

The web interface provided with sql-to-graphql gives you directly access to the associated GraphQL schema.

If you use Graphiql, you can add a route to express to display the schema:

app.use('/schema',function(req,res,next) {
  var printSchema = require('graphql/utilities/schemaPrinter').printSchema;
    res.set('Content-Type', 'text/plain');
    res.send(printSchema(schema));
  });

With our simple database, we have this GraphQL schema:

type RootQueryType {
  user(id: Int!): User
}

type User {
  id: Int!
  firstName: String
  lastName: String
}

Our first GraphQL query

It takes some time to get your first query right.

The nice thing with Graphiql is that it gives you some auto-completion and gives (usually) clear decription when your query is invalid.

After a couple of trial and error, I got my first query :

GraphQL query GraphQL response
query test {
  user(id: 1) {
    firstName
    lastName
  }
}
{
  "user": {
    "firstName": "Zinedine",
    "lastName": "Zidane"
  }
}

Relay schema

Using the --relay flag with the sql-to-graphql CLI, the generated schema is a little bit different: the schema follows the specification required by relay to properly interface with a GraphQL server.

The schema becomes:

interface Node {
  id: ID!
}

type RootQueryType {
  node(id: ID!): Node
}

type User implements Node {
  id: ID!
  firstName: String
  lastName: String
}

As relay doesn’t know anything about your model, it needs some hand holding to be able to query it.

The first change is that relay expects each of your queryable entities to implement the Node interface so that it can query them by id.

Simple?

well…

:rage: aka &^!^*@!!

Let’s try our previous query, by quering a Node type instead of a User type:

GraphQL query GraphQL response
{
  node(id: 1) {
    id
  }
}
{
  "data": {
    "node": null
  },
  "errors": [
    {
      "message": "Type \"\" not a recognized type",
      "locations": [
        {
          "line": 2,
          "column": 5
        }
      ]
    }
  ]
}

What on earth does that message mean? : Type "" not a recognized type.

Where is this empty string coming from? certainly not from line 2 column 5…

a Relay id is not a really your id

This error message is a case of a leaky abstraction.

The Node type is used by relay to query any type of object from your model. Right now, we only have one table and the id 1 is not ambiguous but will be as soon as we have a second table…

Even if we were using a uuid as ids, it would still be impossible to know in which table the id d03b5t54-75b4-431b-adb2-eb6b9e546014 would be coming from.

GraphQL needs to know the type associated to an id.

The way the graphql-relay implementation deals with that is to encode (in that source ) the type name along with the id using a base64 encoder:

export function toGlobalId(type: string, id: string): string {
  return base64([type, id].join(':'));
}

My base64(type:id) is your id

In our example, if we encode in base64 the string: User:1 we get : VXNlcjox and now we can query our node with that query:

GraphQL query GraphQL response
{
  node(id: "VXNlcjox") {
    id
  }
}
{
  "data": {
    "node": {
      "id": "VXNlcjox"
    }
  }
}

Hold your horses my young padawan

Let’s get the firstName now:

GraphQL query GraphQL response
{
  node(id: "VXNlcjox") {
    id
    firstName
  }
}
{
  "errors": [
    {
      "message": "Cannot query field \"firstName\" on \"Node\".",
      "locations": [
        {
          "line": 4,
          "column": 7
        }
      ]
    }
  ]
}

:sob:

This time, the error message explains it all: the query returns a Node and not a User so there is no firstName attribute available.

GraphQL inline fragments

The GraphQL query language allows you to specify fields based on their runtime type using inline fragments.

… and finally:

GraphQL query GraphQL response
{
  node(id: "VXNlcjox") {
    id
    ... on User {
      firstName
        lastName
    }
  }
}
{
  "data": {
    "node": {
      "id": "VXNlcjox",
      "firstName": "Zinedine",
      "lastName": "Zidane"
    }
  }
}

aliases

If you know that the object returned is a User you can give an alias to the Node type so that the result gives you a more natural response:

GraphQL query GraphQL response
{
  user:node(id: "VXNlcjox") {
    id
    ... on User {
      id
      firstName
        lastName
    }
  }
}
{
  "data": {
    "user": {
      "id": "VXNlcjox",
      "firstName": "Zinedine",
      "lastName": "Zidane"
    }
  }
}

Would be nice to get the actual id of the User instance, but I don’t know how to get it.

I guess I am really not that smart after all.

Smart I am not