GraphQL Exploration
Sat, Sep 19, 2015GraphQL 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 :
- a regular GraphQL schema, to simply query your model
- a relay-style GraphQL schema to get a React/Relay ready 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:
- you create and populate your tables in Mysql
- you run the sql-to-graphql CLI to generate the GraphQL schema
- optional: you import the generated schema inside a Graphiql/express server
- you run the web interface to query the model using GraphQL
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.