Using GraphQL with MongoDB
PublishedGraphQL is an alternative to REST endpoints for handling queries and database updates and in this Write Stuff article Igor Ribeiro Lima shows how you can use it with MongoDB and web applications.
GraphQL itself is a way to define a contract of what is provided by the server to a web application. GraphQL tries to improve how clients communicate with remote systems. It comes from a simple idea – instead of defining the structure of responses on the server, the client is given the flexibility to define what it wants in response to its queries. In this article we'll show you everything you need to get going with GraphQL.
Quick introduction
As we know, a REST API typically delivers all the data a client UI might need about a resource and leaves it up to the client to extract the bits of data it actually wants to show. If that data is not a resource it already has, then the client needs to go off to the server and request some more data from another URL. Over time, the link between front-end applications and back-end servers can become pretty rigid. As data gets more complex in structure, it gets harder and slower to query with traditional tools.
Instead, with GraphQL, the UI gets the data it needs in a form that is useful for the UI. The core idea is that the code knows that the data that it needs are on the client, not the server. With GraphQL as an abstraction layer, we hide the complications of web services and let back-end developers create a menu of both available data structures and items available to retrieve (and how to retrieve them). Doing so allows the front-end application - and its developers - the ability to select from the items on the menu as they need. The front-end application doesn't need to worry about new items being added to the menu or where the data for that menu is coming from; that's a task for the GraphQL server.
Let's have a closer look at GraphQL itself...
What is GraphQL
GraphQL is a query language that allows the client to describe the data it needs and its shape by providing a common interface for client and server. It has been designed with a flexible syntax that makes building client applications easier. This interface, between the client and the server, also makes retrieving data and manipulations more efficient because it encourages using only the data needed, rather than retrieving a fixed set of data.
For instance, a Todo
application may have a lot of information for each todo
but let's use GraphQL to request a title
and an id
from a GraphQL server.
query Query {
todos {
id,
title
}
}
Which would produce the resulting data (in JSON format):
{
"data": {
"todos": [
{
"id": "56480ee112171e8ed047f72a",
"title": "Read emails"
},
{
"id": "56480ee812171e8ed047f72d",
"title": "Buy orange"
},
{
"id": "56480eeb12171e8ed047f72e",
"title": "Fix garbage"
}
]
}
}
As you can see, the query defines the format of the response. What the client asks for is what the server will use to return results using the query as a template for JSON objects. GraphQL does not mandate the use of a particular language – JAVA, Ruby and many others have implementations. GraphQL itself is independent of language and protocol being just a specification for queries and responses passed between a client and a server.
A GraphQL server technical preview
GraphQL.js is a reference implementation of GraphQL in JavaScript, and it provides two important capabilities: support for building a type schema, and support for serving queries against that type schema.
To make use of these, first, we'll need to build a GraphQL type schema which maps to our code base. Here's what we'll be mapping in the schema:
//schema.js
var graphql = require ('graphql');
var TODOs = [
{
"id": 1446412739542,
"title": "Read emails",
"completed": false
},
{
"id": 1446412740883,
"title": "Buy orange",
"completed": true
}
];
This is some sample data for us to work with for now. Based on that sample data, let's define a type for each attribute: id
, title
and completed
.
var TodoType = new graphql.GraphQLObjectType({
name: 'todo',
fields: function () {
return {
id: {
type: graphql.GraphQLID
},
title: {
type: graphql.GraphQLString
},
completed: {
type: graphql.GraphQLBoolean
}
}
}
});
Strings and boolean are obvious types while the ID type is a content-agnostic type for carrying unique IDs. Now we have defined the todo
object and described the types for the three fields. The next step is that we need to show how to resolve a query by returning the data through a query type.
var queryType = new graphql.GraphQLObjectType({
name: 'Query',
fields: function () {
return {
todos: {
type: new graphql.GraphQLList(TodoType),
resolve: function () {
return TODOs;
}
}
}
}
});
The resolver is defined in a resolve
property and is a function that returns an array of todo
s. That data is an array in memory, and so the return is a synchronous operation. In case we need to do any asynchronous operations, such as say retrieving data from a database, we can make use of a promise which can wrap the asynchronous operations for us.
To create a promise, we need to create an executor. An executor is a function object with two arguments resolve
and reject
. The first argument fulfills the promise; the second argument rejects it. At the end of the operation, depending on a success or failure case; either one or another should be called.
Look at the same example using promise:
var queryType = new graphql.GraphQLObjectType({
name: 'Query',
fields: function () {
return {
todos: {
type: new graphql.GraphQLList(TodoType),
resolve: function () {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(TODOs)
}, 4000)
});
}
}
}
}
});
It's how a GraphQL query for that should be resolved. Finally we export the queryType
:
module.exports = new graphql.GraphQLSchema({
query: queryType
});
In all, this defines a simple schema with one type and a list of todo
s - of which each elements has three fields - that resolves to a value retrieved from the array.
Second, we need to serve the result of a query against that type schema. We'll want the express, express-graphql and graphql packages.
For brevity, create a package.json
file like this:
{
"name": "todo-graphql-server",
"version": "1.0.0",
"description": "GraphQL server for Todo List",
"main": "server.js",
"dependencies": {
"express": "^4.13.3",
"express-graphql": "^0.3.0",
"graphql": "^0.4.12"
}
}
Then we'll define our own server using those packages:
//server.js
var graphql = require ('graphql').graphql
var express = require('express')
var graphQLHTTP = require('express-graphql')
var Schema = require('./schema')
// This is just an internal test
var query = 'query { todos { id, title, completed } }'
graphql(Schema, query).then( function(result) {
console.log(JSON.stringify(result,null," "));
});
var app = express()
.use('/', graphQLHTTP({ schema: Schema, pretty: true }))
.listen(8080, function (err) {
console.log('GraphQL Server is now running on localhost:8080');
});
Execute npm install
to get dependencies and then do npm start
to run the program. When it runs you should something like this:
$ npm start
> todo-graphql-server@1.0.0 start /home/igor/todos
> node server.js
{
"data": {
"todos": [
{
"id": "1446412739542",
"title": "Read emails",
"completed": false
},
{
"id": "1446412740883",
"title": "Buy orange",
"completed": true
}
]
}
}
GraphQL Server is now running on localhost:8080
The JSON that appears is the result of our internal query. If you open another shell, this will also work:
$ curl -XPOST -H "Content-Type:application/graphql" -d 'query { todos { title } }' http://localhost:8080
{
"data": {
"todos": [
{
"title": "Read emails"
},
{
"title": "Buy orange"
}
]
}
}
You don't need to use cURL
. It's just a simpler way to retrieve data, rather than trying to craft queries in the browser. If your system doesn't have cURL, binaries can be downloaded from the cURL website. It's a great tool to have at hand.
The schema advertised by the server doesn't define how the data is stored. It only describes an API the client can make use of. Getting the data to fulfill the client's requests is an implementation detail of the server.
GraphQL handles adding or changing data as a side-effect of a query. Any operation that intends to have side-effects is called a mutation. Therefore, to define a mutation is very similar to defining a query. The difference is that your resolve method can act on the data before returning results. This is where we can also make use of GraphQL arguments. Like a query, mutations also return typed values. The idea is that if something was modified as part of a mutation, then the mutation also returns whatever was modified.
var MutationAdd = {
type: TodoType,
description: 'Add a Todo',
args: {
title: {
name: 'Todo title',
type: new GraphQLNonNull(GraphQLString)
}
},
resolve: (root, args) => {
var newTodo = new TODO({
title: args.title,
completed: false
})
newTodo.id = newTodo._id
return new Promise((resolve, reject) => {
newTodo.save(function (err) {
if (err) reject(err)
else resolve(newTodo)
})
})
}
}
var MutationType = new GraphQLObjectType({
name: 'Mutation',
fields: {
add: MutationAdd
}
});
export var Schema = new GraphQLSchema({
query: QueryType,
mutation: MutationType
});
The Add
mutation takes a title string and the resolve method for the mutation uses that title to create a new todo
item which it then tries to save using a promise. If successfully saved, the newly created todo
is returned.
Watch out though. While queries happily happen in parallel, allowing the server to process many of them in parallel, mutations are different. Mutations are processed in the order that the server receives them to ensure the integrity of the data in the server. If you send a batch of mutations to the server, they are queued up and stepped through one at a time. This is important to keep in mind and it's worth looking at the documentation to see a fully worked example like this:
{
first: changeTheNumber(newNumber: 1) {
theNumber
},
second: changeTheNumber(newNumber: 3) {
theNumber
},
third: changeTheNumber(newNumber: 2) {
theNumber
}
}
By the end of the request, theNumber
field should have the value of 2
.
Summing up, GraphQL has two types of access:
- queries for fetching (get) data from the server and
- mutations for manipulating (create, update, delete) data.
The next step is to show how to plug that into a database.
Connecting GraphQL to MongoDB
The example code we have used so far gets its data from an array in memory, this particular array:
var TODOs = [
{
"id": 1446412739542,
"title": "Read emails",
"completed": false
},
{
"id": 1446412740883,
"title": "Buy orange",
"completed": true
}
];
Remember the interesting thing about GraphQL is that it makes no assumptions how the data is stored. It does not matter if data comes from memory or any sort of database. The function resolve
defined in the schema is the function that will retrieve data from wherever it resides. Let's recall what we did for a query in the preceding code.
var QueryType = new graphql.GraphQLObjectType({
name: 'Query',
fields: function () {
return {
todos: {
type: new graphql.GraphQLList(TodoType),
resolve: function () {
return TODOs;
}
}
}
}
});
We are going to use the Mongoose driver for MongoDB. Instead of returning an array, we can return a Mongoose promise that it is fetching data from the database. This is how it looks in code:
var mongoose = require('mongoose')
var TODO = mongoose.model('Todo', {
id: mongoose.Schema.Types.ObjectId,
title: String,
completed: Boolean
})
We'll assume the database connection is already open, and create a Mongoose model to back our todo
items.
var QueryType = new GraphQLObjectType({
name: 'Query',
fields: () => ({
todos: {
type: new GraphQLList(TodoType),
resolve: () => {
return new Promise((resolve, reject) => {
TODO.find((err, todos) => {
if (err) reject(err)
else resolve(todos)
})
})
}
}
})
})
Because a database may take some time to complete a query or queries, the GraphQL.js server accepts results or promises to return results. The promise here is called with two functions, resolve and reject, passed into it. Within its code it does a find
and in the find's callback function, if there was an error, it calls the promise's reject function. If all was well, it calls the promise's resolve function with the results. Inside the GraphQL server that rejection or resolution will be picked up and the appropriate results returned.
How to query it
Rather than get you to bring up a server yourself, let's play a little bit with a live demo server we've got set up. First, let's retrieve all data:
query {
todos {
id,
title,
completed
}
}
The resulting data (in JSON):
{
"data": {
"todos": [
{
"id": "56495321c0018b1100108091",
"title": "Read emails",
"completed": false
},
{
"id": "5649532cc0018b1100108092",
"title": "Buy orange",
"completed": false
},
{
"id": "56495337c0018b1100108093",
"title": "Fix garbage",
"completed": false
}
]
}
}
Let's retrieve only the titles now:
query {
todos {
title
}
}
The resulting data (in JSON):
{
"data": {
"todos": [
{
"title": "Read emails"
},
{
"title": "Buy orange"
},
{
"title": "Fix garbage"
}
]
}
}
Then let's add a new todo
and retrieve all id and titles:
mutation {
add(title: "Read a book") {
id,
title
}
}
The resulting data (in JSON):
{
"data": {
"add": {
"id": "568abef565368511002b9698",
"title": "Read a book"
}
}
}
It's up to the client to describe the data it needs. Here we ask for id
, title
and omit completed
. The response format is described in the query and defined by the client instead of the server. In the full version of our example, we have added more other mutations like: toggle
, toggleAll
, destroy
, clearCompleted
and save
. One thing to notice is that we can also pass arguments. Any query or mutation can accept arguments - to create arguments is quite simple and they can be picked up and used in the resolve
function to shape how the query or mutation acts.
Let's React to our GraphQL server
Next, we are going to modify the well-known TodoMVC
example code so that it can use GraphQL. This supports many different web user interfaces to allow easy comparison. For our purposes, we're going to modify the React example. React is a JavaScript library for creating user interfaces by Facebook and Instagram. Many people choose to think of React as the V in MVC. It was built to solve one problem: building large applications with data that changes over time. It's all about building reusable components. In summary, the only thing is to build components.
A Simple React Component
React components implement a render()
method that takes input data and returns what to display. This example uses the XML-like syntax called JSX. Note that JSX is option and isn't required by React, but JSX is very useful. If we want to get at input data passed into the component, it can be accessed within the render()
method through this.props
.
var Application = React.createClass({
render: function() {
return <div>
{ this.props.text }
{ this.props.id }
</div>;
}
});
The JSX compiler takes that code and produces this raw JavaScript:
"use strict";
var Application = React.createClass({
displayName: "Application",
render: function render() {
return React.createElement(
"div",
null,
this.props.text,
this.props.id
);
}
});
The follow image is a JSFiddle with a simple React code:
Running our React Todo locally
First of all we need to get a server up and running to receive our GraphQL queries from the Todo List app. The server core has already been written above.
To run our GraphQL server, execute:
$ git clone https://github.com/igorlima/todo-mongo-graphql-server.git
$ cd todo-mongo-graphql-server
$ npm install
$ npm start
You must have Node v4.0.0 or above. The server code uses ES2015 features that are not supported by older versions.
Any POST requests to the endpoint /graphql
will now be executed against our GraphQL schema. To test that things are working:
$ curl -XPOST -H "Content-Type:application/graphql" -d 'query { todos { title } }' http://localhost:8080
{
"data": {
"todos": []
}
}
We're ready to send mutations to our local server. Let's add a new todo
then:
$ curl -XPOST -H "Content-Type:application/graphql" -d 'mutation { add (title: "Buy magic cube") { id, title } }' http://localhost:8080
{
"data": {
"add": {
"id": "568ac0a31cfd0bd85168df4d",
"title": "Buy magic cube"
}
}
}
In case you are not a big fan of the command line console, just hit http://localhost:8080
in the browser and start writing and testing GraphQL queries by using an in-browser IDE.
Pretty neat huh!? With a server, up and running, we are ready to link it to our Todo List made in React which is a fork of React TodoMVC Example. To download it, execute:
$ git clone -b react-mongo-graphql https://github.com/igorlima/todomvc.git
$ cd todomvc
$ npm install
$ node server.js
Head to http://localhost:3000
to see the running application. Apart from adding the express-graphql package to the application, this code has two major changes in comparison to the original version. Those changes happen inside the examples/react/js
directory.
.
|__ /examples/
| |__ /react/
| |__ /js/
| |__ /app.jsx
| |__ /footer.jsx
| |__ /todoItem.jsx
| |__ /todoModel.js
| |__ /utils.js
|__ package.json
|__ server.js
The .jsx files are untouched but the file examples/react/js/todoModel.js
is changed so it has the information it needs to contact a local GraphQL server. In the images below you can see how the ToDoModel
functions have been changed - the exact changes can be viewed on Github.
Secondly, a proxy in the MVC server receives GraphQL requests and sends them on to the GraphQL server we created. Again, you can see the server code on Github
If you want to try this code without building it, there is a demo on the web:
Wrapping up
That's all. Although GraphQL is relatively new, it does show a useful way of allowing client applications to take the driving seat in what data they retrieve and how they manipulate it, rather than having to adapt to what data a server's API offers. It's a powerful way to offer up the data from your database while retaining control.