GraphQL is a data query language and runtime. It is also a declarative, compositional and strong-typed query language. Useful for querying dynamic data with a strong-typed schema.
The client gets to pick what it needs based on declarative syntax. The data itself is a hierarchical set of fields and queries that resemble the data. Iʼd like to think of it as structured query language without SQL. This makes it easy for a product engineer to describe the data with declarative syntax.
Facebook has used this library since 2012 and it recently went open source. The library has been on GitHub since 2015 and it transpiles on top of Babel.
Type System
One of the features you get with GraphQL is a type system. This type system comes with introspection so you can query the types. This unit test illustrates how I query the sample schema:
const query = `
query IntrospectionHumanTypeQuery {
__type(name: "Human") {
name
}
}
`;
graphql.graphql(schema, query).then(function(result) {
should(result.data.__type.name).equal('Human');
});
The schema
points to a sample schema. I decided to stick with vanilla JavaScript in spite that GraphQL sits on top of Babel. Note that the query
mimics the data I get back. I get a __type.name
which is close to the query of query { __type { name } }
. There is a hierarchy between __type
and name
, just like the structure of the data. Queries return a Promise
so I follow it up by then()
. Feel free to poke around my other introspection tests on the GitHub repo.
Unto the crux of this schema, below is the Human
type I created:
const humanType = new graphql.GraphQLObjectType({
name: 'Human',
fields: {
id: {
type: new graphql.GraphQLNonNull(graphql.GraphQLInt)
},
name: {
type: graphql.GraphQLString
}
}
});
GraphQL gives many options when defining the schema. I get GraphQLInt
, GraphQLString
for example. To make it awesome, Iʼve added a constraint that makes the id
non-nullable.
One big gotcha here is GraphQL schemas need a query type to make them queryable. Below is the query type:
const queryType = new graphql.GraphQLObjectType({
name: 'Query',
fields: {
human: {
type: humanType,
args: {
id: {
type: new graphql.GraphQLNonNull(graphql.GraphQLInt)
}
},
resolve: function(root, args) {
return data[args.id];
}
}
}
});
The queryType
defines the way I expect to query the schema. Note the resolve
callback, this gets called when the Promise
gets fulfilled. You can just point this callback to a real call such as a database lookup. Here, data
is just plain old JSON. This callback executes within an asynchronous context. The same type system used in humanType
applies to queries. I expect to query this schema based on an id
that is a GraphQLInt
and non-nullable.
To finish the schema based on this type system, we need a schema type:
const schema = new graphql.GraphQLSchema({
query: queryType,
types: [humanType]
});
I tell GraphQL precisely what I want. I need a humanType
that gets queried by a queryType
. I hope you can see how the type system comes together. Feel free to poke around my schema code on GitHub.
Validation
The upside of having a humanType
and a queryType
on the schema is validation. Say I want to query the schema without arguments, like the test below:
const query = `
query HumanWithoutArgument {
human {
}
}
`;
graphql.graphql(target, query).then(function(result) {
should.exist(result.errors);
});
Because I put a non-nullable constraint on the query type, this query fails. The test expects to see errors due to constraints on the type. This validation mechanism is a direct gain from the type system. One other validation one gets for free is that id
has to be of type GraphQLInt
. This is an integer type. A query like human(id: "1") { name }
will fail. Adding double-quotes around the number tells the type system to expect a GraphQLString
type. There are many scalar types available that add richness to the validation mechanism. Feel free to poke around the rest of my validation tests on GitHub.
The type system is powerful for expressive queries one can validate. Armed with this arsenal of knowledge, letʼs talk about querying the data.
Queries
Query composition derives from the type system. The example below illustrates this:
http://localhost:1337/?query={human(id:1){id,name}}
This query expects to see a human
type with an integer id
equal to 1
. This human
type contains a hierarchy of field types such as id
and name
. If you go back to the schema, this declarative query is almost identical. The query
query string is just the way I pass the query into GraphQL. Below is the result I get back in application/json
content type:
{
"data": {
"human": {
"id": 1,
"name": "Obi-Wan Kenobi"
}
}
}
I get back a data
type which contains the result of the query. The client gets to decide which field types to query. I could have just queried the name
, for example, such as {human(id:1){name}}
. If I donʼt specify a hierarchy like {human(id:1){}}
then validation returns with a failure.
To set up this GraphQL API in node, one can do:
var app = http.createServer(function(req, res) {
const query = url.parse(req.url, true).query.query;
graphql.graphql(schema, query).then(function(queryResult) {
if (queryResult.errors) {
res.writeHead(400, headers);
res.end(JSON.stringify({
error: queryResult.errors[0].message
}));
} else {
res.writeHead(200, headers);
res.end(JSON.stringify(queryResult));
}
});
});
The url
library gives me a parse
function to get the query string. Once the Promise
gets fulfilled, I use JSON.stringify
to convert to hyper text. The queryResult.errors
is an Error
type. This is why I get a message
property that comes from the Error object in plain JavaScript.
Conclusion
GraphQL leaves it to the imagination to come up with nice ways of slicing and dicing data. Queries that are just objects that come from products in the real world. Feel free to checkout the rest of my demo up on GitHub.
To wrap up this tutorial, we suggest reading our free data sheet on JavaScript Security Threats, which covers the main threats to JS and how to prevent them.