Tutorial

Deep Dive into GraphQL Mutations

Draft updated on Invalid Date
Default avatar

By Peter Ekene

Deep Dive into GraphQL Mutations

This tutorial is out of date and no longer maintained.

Introduction

This is a continuation of the series I’ve been doing on GraphQL Actions. In the last post on this series we worked on a Deep Dive into Queries. This time, we will take a closer look at Mutations. There’s a lot of similarities in the way GraphQL Queries and Mutations work, however, Mutations is more about mutating data than querying for it. In this article, we’ll dive more into it and treat its concepts in detail.

Mutation in any GraphQL application is very essential to the application’s data integrity. Once the mutation is done wrongly, the data output of such application will be wrong and if such data is being used for decision making, it could be catastrophic.

That is why the mutation request object is not like the query request object which you do not need to prefix with a keyword before making query requests. The mutation prefix before the request is to make sure that the request is being intentional and that whoever is making the request knows the consequences of his/her action.

Designing a Mutation schema

Designing a schema for a mutation is quite similar to that of a query. In fact, once you’ve written a query schema, you should have no problem with designing a mutation schema. Let us look at the block of code below which shows us how to write a mutation schema.

let chefs = []
const Mutations = new GraphQLObjectType({
  name: 'Mutation',
  fields: {
    addChef: {
      type: ChefType,
      kwarg: {
        name: { type: GraphQLString },
        age: { type: GraphQLInt},
        hobby: { type: GraphQLInt},
      },
      resolve(parent, kwarg) {
        kwarg.id = Math.random()
        return [...chefs, kwarg]
      }
    },
  }
})

The schema above describes a mutation called addChef which adds the data about a chef to an array (since we are not actually creating a real application). The schema contains some key-value pairs or properties that let GraphQL know that we are trying to create a schema for a Mutation. One of them is the name property which we’ve set to the Mutation. Another one is the fields property which in turn has the other two sub-properties which are addChef and the resolve function.

The addChef object contains information about the Mutation function. It could be named anything. This is the name that would be referred to when we try to mutate data. The kwarg object contains the arguments that the function addChef will expect and their GraphQL data types.

Finally, we have the resolve function which is where all of the mutation actions take place. It takes in two arguments in this instance. One is the parent argument which points to the data linked to the addChef query which we do not have in this instance. So, the parent argument isn’t quite useful here. The second argument is kwarg which refers to the arguments we have passed into the function.

At this point, we have created a mutation but it still won’t work yet. We need to find a way to let GraphQL know that what we’ve done here is a mutation. In order words, we need to find a way of registering the new instance we just created as a mutation and that’s quite easy to do. To register our mutation, we define it like so:

module.exports = new GraphQLSchema({
  mutation: Mutations
})

Creating a Mutation

Having designed a mutation schema and registered it to the application, you can use the mutation addChef in this case, to add data (a chef) to the chefs array. If for instance, we want to create a new chef, we can run this mutation:

mutation {
  addChef(name: "Swae Yu", age: "30", hobby: "Swimming"){
    id,
    age,
    name,
    hobby
  }
}

What we’ve done here is use mutation to add data to the chefs array. You’ll also notice that we had to define the request inside a mutation object. This is so that GraphQL will recognize that we are not just trying to grab data from somewhere but we want to actually alter such data.

Naming Mutations

As much as we can only use the mutation keyword to carry out our mutation action, in production or in more robust applications, it will be better to name individual mutations for clarity and ease of use. That said, we could decide to be more descriptive and give a unique name to the mutation we had in the previous example, this will be the result.

    mutation mutateChef {
      addChef(name: "Swae Yu", age: "30", hobby: "Swimming"){
        id,
        age,
        name,
        hobby
      }
    }

Now we can refer to this mutation with it’s unique name, mutateChef.

Passing query variables to mutations

Consider a situation where we want a user to update the data in our application by passing in new data, maybe from a text input field. This is usually the case in most applications, but at the moment that is not possible in our application since we passed static values directly into our query. What we can do is use variables to pass dynamic data into the query instead.

Thankfully, this is a very straightforward process in GraphQL. Let’s change the mutation query we’ve written previously to use variable inputs:

    mutation ($name: String!, $age: Int!, $hobby: String!){
      addChef(name: $name, age: $age, hobby: $hobby){
        id,
        age,
        name,
        hobby
      }
    }

The snippet above is a good example of how we can modify our mutation query to use input variables instead of the previous static values. The variables are declared using the $ sign and then followed by the name we want to give the variable. Next, we explicitly set a type for the variable. For example, the $name variable was set to a String. The ! after the string shows that the field or variable is required.

Just like in the previous example, we can also give the mutation a unique name. The code snippet below shows a named mutation with dynamic values from variables.

mutation mutateChef($name: String!, $age: Int!, $hobby: String!){
  addChef(name: $name, age: $age, hobby: $hobby){
    id,
    age,
    name,
    hobby
  }
}

Getting our hands dirty!

Now that we’ve had fun understanding what Mutations are all about, It will be nice if we put the knowledge into good use and build a mini GraphQL app to give you a more practical experience.

The application we’ll build should be one that can allow us to perform some basic CRUD operations on a bunch of mock data containing the names of some chefs. In clearer terms, the application will show you how to Create, Update, and Delete a particular chef from an array of chefs using the much-talked-about GraphQL mutation.

Set up a development server

Before we dive into the app, we need to set up a server to send requests to. Let’s get started! First, initialize an empty project with npm init -y. Then proceed with installing all the necessary dependencies for the application:

  1. npm i express express-graphql app-root-path

Once the installation is complete, create a config directory then also created a port.js file in the directory and then update it with the code below:

// config/port.js

export const PORT = process.env.PORT || 9091

Create other project files

In the root directory, create an app.js file that will serve as our entry file to the application. Then update it with the code below:

// app.js - entry file

import express from 'express';
import graphqlHTTP from 'express-graphql';
import { PORT } from './config/port';
import logger from './config/winston';

const app = express();

app.use(cors())
app.use('/graphql', graphqlHTTP({
   graphiql: true
}))

app.listen(PORT, () => {
  logger.info(`Server now listening for request at port ${PORT}`);
})

Finally, go to your package.json file and create a dev script with the code below:

"dev": "nodemon --exec babel-node app.js"

This script command allows us to quickly start our application’s local server without typing so many terminal commands.

Creating the app logic and API

In the root directory, create a Server directory. Then also, create a model and a schema directory. In the model directory, create an index.js file and update it with the code below:

// ./server/model/index.js

export const chefs = [
  {
    id: 1,
    name: "Monique Black"
  },
  {
    id: 2,
    name: "Chidinma Madukwe"
  } ]

Here, we are exporting an array of chefs containing the mock data we will use to represent the initial chefs we have in this application. Once that is done, create an index.js file in the schema directory and update it with the code below:

// ./server/schema/index.js

import {
  GraphQLObjectType,
  GraphQLString,
  GraphQLID,
  GraphQLList,
  GraphQLSchema,
  GraphQLNonNull
  } from 'graphql'
import { chefs } from '../model/'

const ChefType = new GraphQLObjectType({
  name: 'chef',
  fields: () => ({
    id: { type: GraphQLID },
    name: { type: GraphQLString}
  })
})

const RootQuery = new GraphQLObjectType({
  name: 'RootQuery',
  fields: {
    chefs: {
      type: new GraphQLList(ChefType),
      resolve() {
        return chefs
      }
    }
  }
})

const Mutations = new GraphQLObjectType({
  name: 'Mutation',
  fields: {
    deleteChef: {
      type: ChefType,
      args: {
        id: { type: new GraphQLNonNull(GraphQLID)}
      },
      resolve(parent, args) {
        const returnedData = chefs.filter(chef => {
          return chef.id == args.id
        })
        return returnedData[0]
      }
    },
    addChef: {
      type: new GraphQLList(ChefType),
      args: {
        name: {type: new GraphQLNonNull(GraphQLString)}
      },
      resolve(parent, args) {
        args.id = Math.floor(Math.random() * 10)
        return [...chefs, args]
      }
    },
    updateChef: {
      type: ChefType,
      args: {
        id: { type: new GraphQLNonNull(GraphQLID)},
        name: { type: new GraphQLNonNull(GraphQLString)}
      },
      resolve(parent, args) {
        const updateChef = chefs.find(data => {
          return data.id == args.id
        })
        updateChef.name = args.name
        return updateChef
      }
    }
  }
})

export const schema = new GraphQLSchema({
    query: RootQuery,
    mutation: Mutations
})

Yeah, that’s a lot of code but then it’s everything that we’ve been discussing since the beginning of this tutorial. The only few things that were included but not previously covered are the GraphQL types which are:

  • GraphQLObjectType - Which is basically what we use to describe the structure of either a query or a mutation object.
  • GraphQLNonNull - This tells GraphQL that once a file with a GraphQLNonNull type is missing in a query, such query or mutation won’t be made. The field becomes a required field.
  • GraphQLID - This allows a query to be made if an id parameter is being passed in as either a string or an integer, and finally
  • GraphQLList - This lets GraphQL know that the response is going to contain data of a similar type in an array. GraphQL types aren’t just limited to these, the rest can be found on the GraphQL official website.

Finally, go back to the app.js file and update it with the code below:

// app.js - entry file

import express from 'express';
import graphqlHTTP from 'express-graphql';
import cors from 'cors';

import { PORT } from './config/port';
import logger from './config/winston';
import {schema} from './server/schema/';

const app = express();

app.use(cors())
app.use('/graphql', graphqlHTTP({
  schema,
  graphiql: true
}))

app.listen(PORT, () => {
  logger.info(`Server now listening for request at port ${PORT}`);
})

Start the server

After grabbing the code, run the npm run dev script command on your terminal to start the local server. You should get this message logged to the console when your server is running successfully.

"message":"Server now listening for request at port 9090","level":"info"}

Testing the Mutations on Graphiql

Now that we have a server, let’s test our application with Graphiql. Graphiql is a GUI application that is shipped or embedded into the express-graphql module we installed earlier. To open it up, go to your browser and visit this URL http://localhost:9091/graphql. You should see a page similar to the one below:

To create a query that will return all the chefs in our existing dataset, update the left-han side of the GUI with the snippet below:

{
  chefs {
    id,
    name
  }
}

Now click the Play button on the GUI and you should have the same output like the one below:

As expected, it returns all the static data we currently have in our chefs array.

Create new chef

To create a mutation that adds a new chef to the existing chefs dataset, we pass in a new name to the addChef function we defined in the schema. Here’s an example code to add a new chef with the name “Chinwe Eze”:

mutation{
  addChef(name: "Chinwe Eze"){
    id,
    name
  }
}

You can verify this functionality by running this snippet in the Graphiql GUI, you should get the same behavior as mine below:

If you’re wondering why we have an id of 6 for the new chef, it is because we are generating random IDs for every new chef. You can check back on the schema to verify this feature.

Update existing chef

If we wanted to update existing data, we could redefine our mutation and use the updateChef function we defined in the schema to modify existing data. A typical use case would be a user who wants to update their profile information.

In our case, if we wanted to update the name of an existing chef, all we need to do is pass in the id of the chef with the new name to the updateChef function. Let’s update the chef with the name Monique Black to Simona White:

You’ll notice that this mutation is quite different from the rest because it takes in two arguments. However, a mutation function isn’t just limited to one or just two arguments, it can take as many arguments as you want. The only thing you should be wary about is whether you are passing in the right arguments.

Delete a chef

Just like every other previous operation we’ve performed with our mutation, we can delete existing data, in our case, we’ll delete a chef from our existing array of chefs. Again, we can pass in the id of a chef to the deleteChef function we defined in the schema and the chef will be removed.

Conclusion

In this post, we took an in-depth look at GraphQL mutations. We demonstrated its concepts like designing mutation schemas, naming and testing mutations, passing in query variables to our mutations, and so on. We went further to create a mini GraphQL project to give you a more practical example of working with GraphQL mutation in real-world applications.

Summarily, Mutation provides us a very flexible way to perform the CUD in CRUD (Create, Read, Update, Delete) without really going through complex data queries or programming logic. Once a schema has been created, and a relationship exists between related data, then there is not much programming to be done to get the needed data. In my opinion, GraphQL generally makes API design more interesting and less complex for us developers.

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about us


About the authors
Default avatar
Peter Ekene

author

Still looking for an answer?

Ask a questionSearch for more help

Was this helpful?
 
Leave a comment


This textbox defaults to using Markdown to format your answer.

You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!

Try DigitalOcean for free

Click below to sign up and get $200 of credit to try our products over 60 days!

Sign up

Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

Get our biweekly newsletter

Sign up for Infrastructure as a Newsletter.

Hollie's Hub for Good

Working on improving health and education, reducing inequality, and spurring economic growth? We'd like to help.

Become a contributor

Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.

Welcome to the developer cloud

DigitalOcean makes it simple to launch in the cloud and scale up as you grow — whether you're running one virtual machine or ten thousand.

Learn more
DigitalOcean Cloud Control Panel