SHARE

In the following post we’ll quickly explain what GraphQL is and why you might want to consider it. GraphQL is a querying language for APIs, and we have been using it as an alternative to REST.

Why not use REST you may wonder? There are a few advantages to using GraphQL. The first is that it helps abstract some of the data constructs which can be particularly helpful in your frontend models. Another advantage is one can limit the payload data to only get the data you need rather than parse out the entire payload after transport. This is particularly important in a mobile dominated world where saving every bit of bandwidth is a value-add. Also useful to mobile, you can get many resources in a single request (batch) which helps avoid loading of multiple REST URLs. Last, GraphQL can help ease the burden of maintainability when your data does change.

This post, however, isn’t meant to be an introduction to GraphQL. Instead, we will show you through examples and discussion of more advanced topics related to building a GraphQL server and how GraphQL has been useful for us. Specifically, we will be writing a server in Node.js that is designed to be constructed by multiple engineers. Therefore, it might be helpful to familiarize yourself with the basics of GraphQL. A good place to start is an introduction to GraphQL or how to GraphQL.

The examples used in the following post come from our GraphQL wrapper for the Joyent CloudAPI. The wrapper itself is a hapi plugin that relies on graphi, which is a hapi plugin designed to make building GraphQL servers in hapi a straightforward task.

Simple example of a GraphQL request

Before we look at the server code we need to have an understanding of what a GraphQL schema looks like and how to interact with it from a clients perspective. GraphQL requests are simple to construct with tools that you already have in your toolkit. For example, you can make GraphQL requests to a server simply using curl. Additionally, there is an Node.js module named GraphiQL that is an interactive IDE designed to construct queries and execute them against a server. This is the tool that we tend to use when building out more sophisticated queries.

We will look at the virtual machine section of the example data object from the GraphQL wrapper. The following schema represents a virtual machine. It is simplified and incomplete, but will illustrate some example object types. Even though the following is a snippet from the overall schema, it actually contains enough data to be understood by the GraphQL JavaScript library.

type Machine { # Unique id for this machine id: ID # The “friendly” name for this machine name: String # The current state of this machine (e.g. running) state: MachineState # The image this machine was provisioned with image: Image } enum MachineState { PROVISIONING RUNNING STOPPING STOPPED DELETED } type Image { # Unique id for this image id: ID # The “friendly” name for this image name: String }

In addition to defining the various object types, a schema should also define what methods are supported. In GraphQL the methods are contained in a type definition for either Query, Mutation, or Subscription. For the purposes of this example, the Query type will only contain a single method to retrieve a machine, as shown below:

type Query { machine(id: ID): Machine }

From the above information we are now able to construct the following query to retrieve the name of a machine.

query { machine(id: “SOME-UUID” ) { name } }

We can even convert this into an HTTP request using curl, which might look similar to the following:

curl -X POST http://localhost/graphql -H “Content-Type: application/json” -d ‘{ “query”: “query { machine(id: “SOME-UUID” ) { name } }” }’

The response will be JSON and contain something similar to the following structure:

{ “data”: { “machine”: { “name”: “foo” } } }

GraphQL supports multiple queries being sent as part of a single request, which is the reason why the machine result being contained in the data object. Later in this post we will provide an example of submitting multiple queries in a single request. If the query for a machine is successful, then the result will be contained in a field with the key of the query, which is machine. If there is an error, the result will be contained in an errors field.

Handling a GraphQL request on the server

When describing the underlying function that processes a HTTP request, most languages and frameworks will use the term handler. The handler function has varying responsibilities depending on what framework or design patterns are used, but generally speaking, it’s responsible for performing some action or providing the appropriate response to whatever it was asked about. In GraphQL, instead of using the term handler, the specification uses the term resolver. Therefore, when you read through the following code snippets and see the term resolver, think of it as an operation handler.

In order to help make it easy to get started with GraphQL in Node.js—particularly with existing hapi developers—we have created a hapi plugin called graphi. When registering graphi, you will either provide it with a list of resolvers that align to the queries and mutations found in the GraphQL schema, or you can let graphi map hapi routes as resolvers. For example, if we want to provide a resolver for retrieving a machine for a particular id, then we can create a resolver like the following:

const getMachine = function ({ id }) { return { id, name: ‘foo’, state: ‘RUNNING’, image: {} // populate with image properties }; };

As you can see, the getMachine function expects a single argument id and will return an object matching the schema for a machine. Resolvers are usually asynchronous operations, therefore it’s perfectly safe to return a promise, which will be awaited on.

In order to utilize this with hapi, we can register the graphi plugin and reference the resolver and schema, as shown below:

const Graphi = require(‘graphi’); const Hapi = require(‘hapi’); // read schema from separate .schema file const main = async () => { const server = Hapi.server({ port: 8080 }); const resolvers = { machine: getMachine // from previous example }; await server.register({ plugin: Graphi, options: { schema, resolvers } }); await server.start(); }; main();

After the server is started, a new route is added which accepts requests found at the /graphql path. The endpoint is capable of accepting the following request and responding with the following response.

$ curl -X POST “http://localhost:8080/graphql” -H “Content-Type: application/json” -d ‘{ “query”: “query { machine(id: “84f5497f-11f7-4e09-890a-58dd3517b685″) { name } }” }’ {“data”:{“machine”:{“name”:”foo”}}}

The interesting thing to note is that we retrieved and populated the image in the resolver, even though the client didn’t ask us for information about the image. This is a carry-over design from how we would prepare responses in a REST world. However, now that we are using GraphQL and we can see exactly what fields the client is concerned with receiving, we can remove this extra processing. We can do this by using the resolver formatters feature which graphi supports. Alongside the individual query and mutation resolvers that we populate on the resolvers option, we can provide an object for each type defined in the schema for providing further formatting.

LEAVE A REPLY

Please enter your comment!
Please enter your name here