Zac Fukuda
026

GraphQL + MongoDB: Basic – Mutation

The source code is available on Github.

This is a continued tutorial of previous article GraphQL + MongoDB: Basic – Query. All codes shown in this article are based upon codes that was being used in it. So if you haven’t followed the previous article yet, please follow that first.

I will skip explaining what is GraphQL or why I am writing this article. So let’s dive into code.

Mutation: add

As a mutation, we are going to make a program that adds new book data.

Method 1

Just as we created Query type object first when we defined Query, let’s create a file called MutationType.js that defines Mutation type:

$ touch method1/ObjectType/MutationType.js

And please add following code to the file:

./method1/ObjectType/MutationType.js
const path = require('path')
const {
  GraphQLObjectType,
  GraphQLNonNull,
  GraphQLString
} = require('graphql')
const assert = require('assert')
const BookType = require('./BookType')
const { collectionName } = require(path.join(process.cwd(), 'config'))

module.exports = new GraphQLObjectType({
  name: 'Mutation',
  fields: {
    addBook: {
      type: BookType,
      description: 'Add a new book',
      args: {
        title: {
          name: 'Book title',
          type: new GraphQLNonNull(GraphQLString)
        },
        author: {
          name: 'Author of book',
          type: new GraphQLNonNull(GraphQLString)
        }
      },
      resolve: (root, args) => {
        return new Promise( (resolve, reject) => {
          const collection = root.db.collection(collectionName)
          collection.insert({
            title: args.title,
            author: args.author
          }, (err, result) => {
            assert.equal(err, null);
            console.log('New document inserted')
            // console.log(result)
            resolve(result.ops[0])
          })
        })
      }
    },
  }
})

We just defined one field of Mutation type called addBook. This query accepts two argument title and author which will be added to MongoDB, and resolver just defines this process.

After making and defining Query type, let’s add it to our schema that we created in previous article. Our new schema looks like this.

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

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

So far so good. Last thing we need is a file that handles all mutating process. Please create a file called mutation.js inside method1 folder and add the code shown below:

$ touch method1/mutation.js
./method1/mutation.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 root = {
    db: client.db(dbName)
  }

  // Querying (Mutation)
  let query = `mutation {
    addBook(
      title: "Good to Great",
      author: "Jim Collins"
    ) {
      title, author
    }
  }`
  graphql(schema, query, root).then( result => {

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

    client.close()
  })
})

See what we query here: mutation { addBook(title: "Good to Great", author: "Jim Collins") {title, author} }. With this query GraphQL server will add data of book title: "Good to Great" and author: "Jim Collins" to database.

Please run the following command to add the data:

$ node method1/mutation.js

After running this command, the terminal shows something like:

{"data":{"addBook":{"title":"Good to Great","author":"Jim Collins"}}}

To check whether new data is added to the database or not, please run node method1/query.js. If you see at the end of the returned data what you just queried with mutation addBook, your mutation program is working properly. If you do not like to add the given book data, feel free to change it.

Method 2

Next step is to write Mutation addBook with buildSchema. Please add the following code to schema.js inside method2 folder:

./method2/schema.js
...

module.exports = new buildSchema(`
  type Book {
    ...
  }

  type Query {
    ...
  }

  type Mutation {
    addBook(title: String!, author: String!): Book!
  }
`)

After adding schema, let’s add resolver function that handles addBook mutation. Please add the following code to the resolvers.js inside method2 folder:

./method2/resolvers.js
...

const resolvers = {
  books: async () => {
    ...
  },
  addBook: async (args, context, info) => {
    
    var newBook = new Book({
      title: args.title,
      author: args.author
    })

    // Save a new book to DB
    var err = await newBook.save()

    if (err) return err
    return newBook
  },
}

...

Please do not forget to add a comma(,) after books: async () => {}.

Now it is time to wrap everything up. Please create a file called mutation.js inside method2 folder and add the code shown below:

$ touch method2/mutation.js
./method2/mutation.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 (Mutation)
var query = `mutation {
  addBook(
    title: "Good to Great",
    author: "Jim Collins"
  ) {
    title, author
  }
}`

// For test
const contextValue = {
  text: 'hello'
}

graphql(schema, query, resolvers, contextValue).then( response => {

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

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

In the code above, I defined contextValue in order to see if that value is correctly passed to the resolver. It is not necessary here, so you can eliminate it.

What this mutation program does is completely same as in Method 1. Let’s test the program:

$ node method2/mutation.js

This command again returns something like {"data":{"addBook":{"title":"Good to Great","author":"Jim Collins"}}}. You can run query program to see if the data is successfully added.

Since you run mutation command twice, you see two Good to Great in data when you query books.

Mutation: remove

Although the end code of removing mutation would be very similar to the adding one, as a bonus content I will show you how to remove book data from MongoDB with GraphQL.

Method 1

Since we have already created Mutation type object in the previous section, let’s add a new field called removeBook to it. Please add the following code to MutationType.js:

./method1/ObjectType/MutationType.js
...

module.exports = new GraphQLObjectType({
  name: 'Mutation',
  fields: {
    addBook: {
      ...
    },
    removeBook: {
      type: BookType,
      description: 'Remove a new book',
      args: {
        title: {
          name: 'Book title',
          type: new GraphQLNonNull(GraphQLString)
        }
      },
      resolve: (root, args) => {
        return new Promise( (resolve, reject) => {
          const collection = root.db.collection(collectionName)
          collection.findOneAndDelete({
            title: args.title
          }, (err, result) => {
            assert.equal(err, null);
            console.log('Matched document removed.')
            // console.log(result)

            // Document data is joined as "value"
            resolve(result.value)
          })
        })
      }
    }
  }
})

Please do not forget to add a comma(,) after addBook: {}.

The Mutation type is already imported to our schema. Hence, let us move on and create a new file called mutationRemove.js and add the code shown below:

$ touch method1/mutaionRemove.js
./method1/mutaionRemove.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 root = {
    db: client.db(dbName)
  }

  // Querying (Mutation)
  let query = `mutation {
    removeBook(
      title: "Good to Great"
    ) {
      _id, title, author
    }
  }`
  graphql(schema, query, root).then( result => {

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

    client.close()
  })
})

With the program above, we will remove one book data that the title of which matches Good to Great. Now you can run:

$ node method1/mutationRemove.js

This will command returns data that looks like:

{"data":{"removeBook":{"_id":"5b05076e338128079c94f1ff","title":"Good to Great","author":"Jim Collins"}}}

Again, you can see if the data is removed by executing node method1/query.js.

If you have added to two books data while proceeding the tutorial, you still see one Good to Great data returned.

Method 2

Now let’s write mutationRemove with buildSchema. Please add the following code to schema.js inside method2:

./method2/schema.js
...

module.exports = new buildSchema(`
  type Book {
    ...
  }

  type Query {
    ...
  }

  type Mutation {
    addBook(title: String!, author: String!): Book!
    removeBook(title: String!): Book!
  }
`)

And add the following code to resolvers.js:

./method2/resolvers.js
...

const resolvers = {
  books: async () => {
    ...
  },
  addBook: async (args, context, info) => {
    ...
  },
  removeBook: async (args, info) => {
    var doc = await Book.findOneAndRemove({
      title: args.title
    })

    return doc
  }
}

...

Then, please create a new file called mutationRemove.js inside method2 folder and add the code shown below:

$ touch method2/mutationRemove.js
./method2/mutationRemove.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 (Mutation - remove)
var query = `mutation {
  removeBook(
    title: "Good to Great"
  ) {
    _id, title, author
  }
}`

graphql(schema, query, resolvers).then( response => {

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

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

You can try the program by:

$ node method2/mutationRemove.js

After running this command, you see the similar result to the previous removing mutation in Method 1. And please make sure that while you run mutationRemove, there is at least one book data that has Good to Great in its title value.

Next Step

You can add new query or mutation method to GraphQL schema, or modify query you send to GraphQL server to see if how the result would change.

Next step is to create an Express application which bases on the same GraphQL schema that we just created so that we can test our GraphQL API from browser with the support of GraphiQL. If you are interested, please follow up the next article GraphQL + MongoDB: Express.

Resources