Zac Fukuda
025

GraphQL + MongoDB: Basic – Query

The source code is available on Github.

If you are reading this article, you probably think that now GraphQL is a new, hot technology, and are trying to catch up the wave of technological changes. Having followed How to GraphQL’s tutorials, you may wonder what to do next.

How to GraphQL’s tutorials are amazing to learn how to build practical web application with GraphQL. It is, however, quiet obscure what is going on database because all backend features come with tools such as graphql-yoga, apollo-server, and Prisma. With these tool we can deploy GraphQL server and its application by typing a few command and writing least code. Yes, that’s what they are designed to do. Nevertheless, when I followed their tutorial, I want to understand more basic and fundamental concept. For example, I wanted to run GraphQL server that communicate with MongoDB, not MySQL that currently Prisma supports. That is why I decided to write this tutorial.

There is a discussion to develop MongoDB version of Prisma. You can check that out at their Github issue.

In this and next articles, I am going to show you how to create Terminal-based simple program that implements GraphQL API to communicate with MongoDB.

Prerequisites

In order to follow this tutorial you are required to have…

  • Node.js & NPM, and Yarn installed.
  • MongoDB installed.
  • Previous experience in Node.js application development with MongoDB

As of this writing, I tested the sample code shown below with Node.js v8.11.1 and MongoDB v3.4.4. I cannot guarantee that the code I give you here works with the later version of Node.js and MongoDB. So if you encounter any bugs due to the version difference, please find a issue and solution on your own.

A programmer who is new to programming might be able to follow the tutorial. Yet, I encourage you to try REST API before trying GraphQL.

Two ways to use GraphQL.js

There are basically two approaches to use GraphQL.js

Method 1 – GraphQLObjectType

The first approach to setup GraphQL server is to use GraphQLObjectType. This is the initially proposed way to define GraphQL schema that is eventually passed down to GraphQL server. This approach is still documented in their official Git repository.

Method 2 – buildSchema

The second approach, which is now an official way to use GraphQL.js, is to use buildSchema function. I show you GraphQLObjectType approach just in case in order to give some basic idea of “what GraphQL is,” however, now using buildSchema is dominant. (Frankly, I think now nobody takes GraphQLObjectType approach.)

In Method 2, I also use Mongoose for MongoDB object modeling.

Before Getting Started

From now on, you should follow the tutorial from your working directory in terminal.

$ cd path/to/your/working/directory

// Or if you download repository from Github
$ cd ./graphql-mongo/Basic

Keep Running MongoDB Demon

While you run application, please make sure that your local MongoDB is up running. Otherwise, you cannot make a connection to the database and your application fails. You can simply do this by executing following command from terminal:

$ mongod

Initialize Project and Install Packages

Let us initialize an NPM project and install packages needed:

$ yarn init
$ yarn add graphql mongodb mongoose

// Or if you downloaded repository from Github
$ yarn

After executing yarn init command, it prompts a wizard. Please answer to some questions. You can simply keep clicking enter if you want.

Seed Data

It is possible to generate initial data with GraphQL but we want to try querying first. Thus, we generate those data with simple Javascript code. Please make two files called config.js andseed.js at the root of your working directory and add following code:

./config.js
module.exports = {
  dbURL: 'mongodb://localhost:27017',
  dbName: 'graphql',
  collectionName: 'books'
}
./seed.js
const { MongoClient } = require('mongodb')
const assert = require('assert')
const { dbURL, dbName, collectionName } = require('./config')

let books = [
  {
    'title': 'David and Goliath',
    'author': 'Malcolm Gladwell'
  },
  {
    'title': 'Steve Jobs',
    'author': 'Walter Isaacson'
  }
]

// Connect to the server
MongoClient.connect(dbURL, function(err, client) {
  assert.equal(null, err)
  console.log('Connected successfully to server')

  const db = client.db(dbName)
  const collection = db.collection(collectionName)

  collection.insertMany(books, (err, result) => {
    if (err) {
      console.log(err)
    } else {
      console.log('Documents inserted!')
    }

    client.close()
  })
})

Please note that in this tutorial I am going to use database called graphql. Since it is a common name, if you already have a database in the same name, please replace dbName with anything you like.

This code generates two books data: David and Goliath by Malcolm Gladwell and Steve Jobs by Walter Isaacson, which we will query later. After making file, please run the next command to generate data:

$ node seed.js

Create Common files

At this point, please create following folders and files which are going to be used throughout the tutorial.

$ mkdir method1 method2
$ mkdir method1/ObjectType method2/MongooseModel
$ touch method1/ObjectType/BookType.js
$ touch method2/MongooseModel/Book.js

After this, please add following codes to each file:

./method1/ObjectType/BookType.js
const {
  GraphQLObjectType,
  GraphQLString,
} = require('graphql')

module.exports = new GraphQLObjectType({
  name: 'book',
  fields: {
    _id: {
      type: GraphQLString
    },
    title: {
      type: GraphQLString
    },
    author: {
      type: GraphQLString
    }
  }
})
./method2/MongooseModel/Book.js
const mongoose = require('mongoose')

// Mongoose Model
const bookSchema = new mongoose.Schema({
  title: String,
  author: String
})

// Export Mongoose "Book" model
module.exports = mongoose.model('Book', bookSchema)

Now it is time to try GraphQL.

Query

Method 1

First thing you should do is to create a Query type object. So let’s create a file that defines that.

$ touch method1/ObjectType/QueryType.js

And please add the following code to the file:

./method1/ObjectType/QueryType.js
const path = require('path')
const {
  GraphQLObjectType,
  GraphQLList,
} = require('graphql')
const BookType = require('./BookType')
const { dbURL, dbName, collectionName } = require(path.join(process.cwd(), 'config'))

module.exports = new GraphQLObjectType({
  name: 'Query',
  fields: {
    books: {
      type: new GraphQLList(BookType),
      resolve: (db) => {
        return new Promise( (resolve, reject) =>{
          const collection = db.collection( collectionName )
          collection.find().toArray((err, docs) => {
            if (err) reject(err)
            else resolve(docs)
          })
        })
      }
    }
  }
})

Simple enough, what this object does is that when GraphQL server receives query of Query with books field, it fetches data from MongoDB and then returns that data.

Now next thing you should do is to define a schema. Let’s create a new file called schema.js:

$ touch method1/schema.js

And please add the following code:

./method1/schema.js
const { GraphQLSchema } = require('graphql')
const QueryType = require('./ObjectType/QueryType')

module.exports = new GraphQLSchema({
  query: QueryType,
})

schema.js, to which Mutation type willed added later, is like a file that describes what type of query can be processed. But for now all we need is just Query type.

After defining Query type and minimum schema, it time to wrap up those files into single querying file. Please create query.js.

$ touch method1/query.js

Then, add the following code:

./method1/query.js
const path = require('path')
const { MongoClient } = require('mongodb')
const { graphql } = require('graphql')
const assert = require('assert')
const { dbURL, dbName, collectionName } = require(path.join(process.cwd(), 'config'))

// GraphQL Schema
const schema = require('./schema.js')

// Connect to DB
MongoClient.connect(dbURL, (err, client) => {
  assert.equal(null, err)
  console.log('Connected successfully to server')

  const db = client.db(dbName)

  // Querying
  var query = '{ books { title, author } }'
  graphql(schema, query, db).then( result => {

    // In JSON format
    console.log( JSON.stringify(result) )

    client.close();
  })

})

In this file, after making a connection to database, we are going to send a query request { books { title, author } } to GraphQL server which is given a schema that we just defined. As we wrote earlier, GraphQL server will fetch all books data from database. Now let’s test our code:

$ node query.js

This command will return data that looks like this:

{"data":{"books":[{"title":"David and Goliath","author":"Malcolm Gladwell"},{"title":"Steve Jobs","author":"Walter Isaacson"}]}}

Here, in order to display all data within result, I use JSON.stringfy(). If you want to see what is going to happen if we simply log result, you can try that on your own. Plus, you can modify query variable to change the data structure of result. For example, you can send a query request { books { title } } which will return only the title of book.

Method 2

In method 1, there basically needs to be two files: one that defines Query type and another that defines schema. In method 2, there needs to be two files again, but those files define schema and resolver. With buildSchema, you can define Query type much simpler and easier by separating resolving function to another file.

(In method 1, you could write the code of QueryType.js in schema.js directly. Or, ultimately you could write all code inside query.js. Nevertheless, I try to separate code into individual file for each responsible.)

Before defining resolver, let us crete a file called schema.js inside method2 folder.

$ touch method2/schema.js

And the inside of the file looks something as follows:

./method2/schema.js
const { buildSchema } = require('graphql')

module.exports = new buildSchema(`
  type Book {
    _id: String
    title: String!
    author: String!
  }

  type Query {
    books: [Book]
  }
`)

Probably by now you know what [Book] means, but I explain anyway. In programming language, when you define array of something you use []. Therefore, what [Book] means is that the result of Query books will be array of Book, which is defined right before Query inside schema.js.

Next let’s create a resolver file called resolvers.js:

$ touch method2/resolvers.js

And please add the following code to the file:

./method2/resolvers.js
const Book = require('./MongooseModel/Book')

const resolvers = {
  books: async () => {
    return (await Book.find())
  },
}

module.exports = resolvers

Now again let us put the all things together into one querying file. Please create a new file called query.js inside method2 folder and add the code shown below:

$ touch method2/query.js
./method2/query.js
const path = require('path')
const mongoose = require('mongoose')
const { graphql } = require('graphql')
const { dbURL, dbName } = require(path.join(process.cwd(), 'config'))

// GraphQL Schema
const schema = require('./schema')

// Resolvers
const resolvers = require('./resolvers')

// Connect to DB
mongoose.connect(dbURL + '/' + dbName)

// Querying
var query = '{ books { title, author } }'
graphql(schema, query, resolvers).then( (response) => {

  // In JSON format
  console.log( JSON.stringify(response) )

  // Disconnect from DB
  mongoose.connection.close()
})

After writing querying program, please run a command below:

$ node method2/query.js

If you see the same data that was shown in the method 1, your program is working properly and you successfully wrote a simple GraphQL sever with buildSchema.

Next Step

Please follow up next article GraphQL + MongoDB: Basic – Mutation that shows you how to do Mutation with GraphQL and MongoDB. All codes in the next article are written upon the code shown in this article.

Resources