GraphQL Mutations

Types


Mutations are the only way to change data in a GraphQL server. Involves creating a new type, and then adding a mutation field to the schema. Creating, updating, and deleting data is a common use case for mutations. The data format is similar to the data format for queries. Contain the following types:

  • Object: The type of the object that is being mutated. The object type is the same as the one that is being queried.
  • Root: The root type of the mutation.
  • Input: The input type for the mutation.Is used to validate the mutation.

Let's do a quick example of a mutation. Create a folder called graphql-api at the root of the project. Then create a file called server.js in the graphql-api folder. Install the necessary packages in the command line:

npm install express-graphql graphql
npm install -D nodemon

or with yarn:

yarn add express-graphql graphql
yarn add -D nodemon

The full example is available here.

ES6 syntax is used in this example, which implies import and export, instead of the traditional require and module.exports. To enable ES6 syntax, use the --experimental-specifier-resolution flag. In the package.json file, add the following properties:

"name": "graphql-api",
"version": "1.0.0",
"type": "module",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \" Error: no test specified \ "&& exit 1",
"start": "nodemon --experimental-specifier-resolution = node server.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"express": "^ 4.17.1",
"express-graphql": "^ 0.12.0",
"graphql": "^ 15.5.1",
"nodemon": "^ 2.0.12"
}
}

In the server.js file, add the following code:

import express from 'express';
import { graphqlHTTP } from 'express-graphql';
import { GraphQLSchema } from 'graphql';
import RootQueryType from './schema/query.js';
import RootMutationType from './schema/mutation.js';
// Define the schema
const schema = new GraphQLSchema({
query: RootQueryType,
mutation: RootMutationType
});
const app = express();
app.use('/graphql', graphqlHTTP({
schema: schema,
graphiql: true,
}));
const port = process.env.PORT || 8080;
app.listen(port, () => {
console.log(`Running a GraphQL API server at http://localhost:${port}/graphql`);
});

graphqlHTTP is a function that takes a few options. The first option is the schema. The schema is the root of the query and mutation types. The second option is the graphiql flag. If it is set to true, the GraphiQL interface( playground ) will be available at the root of the server. In the schema, we also define the input type and resolvers for the mutation.

GraphQL Schema


Create a file called schema.js in the graphql-api folder. Then create a file called query.js and mutation.js in the graphql-api folder. In the query.js file, add the following code:

import {
GraphQLObjectType,
GraphQLID,
GraphQLList,
} from 'graphql';
import Users from './data/users.js';
Import Posts from './data/posts.js';
import userType from './types/user.js';
import postType from './types/post.js';
const RootQueryType = new GraphQLObjectType ({
name: "Query",
fields: () => ({
user: {
type: userType,
args: { id: {type: GraphQLID }},
resolve (parent, args) {
const user = Users [args.id - 1];
return user;
},
},
users: {
type: GraphQLList(userType),
resolve (parent, args) {
const users = Users;
return users;
},
},
post: {
type: postType,
args: {
id: { type: GraphQLID },
},
resolve (parent, args) {
const post = Posts [args.id - 1];
return post;
},
},
posts: {
type: new GraphQLList(postType),
resolve (parent, args) {
const posts = Posts;
return posts;
},
},
}),
});
export default RootQueryType;

A query type must exist in the schema. You can create a query type by using the new GraphQLObjectType function. The name property is the name of the type. The fields property is a function that returns an object with the fields of the type. The args property is an object with the arguments of the field. The type property is the type of field. The resolve property is a function that returns the data for the field.

Mutation


In the mutation.js file, add the following code:

import { GraphQLNonNull, GraphQLObjectType } from 'graphql';
import Users from './data/users.js';
import Posts from './data/posts.js';
import userType from './types/user.js';
import postType from './types/post.js';
import { inputUserType, inputPostType } from './types/input.js';
const RootMutationType = new GraphQLObjectType ({
name: "Mutation",
fields: () => ({
createUser: {
type: new GraphQLNonNull(userType),
args: {
input: { type: new GraphQLNonNull(inputUserType) },
},
resolve (parent, args) {
const user = {
id: Number(args.input.id),
username: args.input.username,
email: args.input.email,
};
Users.push(user);
return user;
},
},
createPost: {
type: new GraphQLNonNull(postType),
args: {
input: {type: new GraphQLNonNull(inputPostType)},
},
resolve: async (parent, args) => {
const post = {
id: Number(args.input.id),
title: args.input.title,
text: args.input.text,
user_id: Number(args.input.user_id),
};
Posts.push(post);
return post;
},
},
}),
});
export default RootMutationType;

Input Types


In the schema folder, create a new folder called types. Then create a file called input.js in the types folder. In the input.js file, add the following code:

import {GraphQLID, GraphQLInputObjectType, GraphQLNonNull, GraphQLString} from 'graphql';
export const inputUserType = new GraphQLInputObjectType ({
name: "UserInput",
fields: () => ({
id: { type: GraphQLID },
username: { type: new GraphQLNonNull(GraphQLString) },
email: { type: new GraphQLNonNull(GraphQLString) },
}),
});
export const inputPostType = new GraphQLInputObjectType ({
name: "PostInput",
fields: () => ({
id: { type: GraphQLID },
title: { type: GraphQLString },
body: { type: GraphQLString },
user_id: { type: GraphQLID },
}),
});

Then create our custom types. In the user.js file, add the following code:

import {GraphQLID, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLString} from 'graphql';
import postType from './post.js';
import Posts from '../../data/posts.js';
const userType = new GraphQLObjectType({
name: "User",
fields: () => ({
id: { type: new GraphQLNonNull(GraphQLID) },
username: { type: new GraphQLNonNull(GraphQLString) },
email: { type: new GraphQLNonNull(GraphQLString) },
posts: {
type: new GraphQLList(postType),
resolve(parent, args) {
const posts = Posts.filter(
(posts) => posts.user_id === JSON.stringify(parent.id)
);
return posts;
},
},
}),
});
export default userType;

In the post.js file, add the following code:

import {GraphQLID, GraphQLNonNull, GraphQLObjectType, GraphQLString} from 'graphql';
import userType from './user.js';
import Users from '../../data/users.js';
const postType = new GraphQLObjectType({
name: "Post",
fields: () => ({
id: { type: new GraphQLNonNull(GraphQLID) },
title: { type: new GraphQLNonNull(GraphQLString) },
text: { type: new GraphQLNonNull(GraphQLString) },
user_id: { type: new GraphQLNonNull(GraphQLID) },
user: {
type: new GraphQLNonNull(userType),
resolve(parent, args) {
const user = Users[parent.user_id - 1];
return user;
},
},
}),
});
export default postType;

Now it remains to start the server with the command in the terminal:

npm start

To test the server, open the browser and type the following URL: http://localhost:4000/graphql and put the following mutation with arbitrary data:

mutation {
createUser (input: {id: 4, username: "dragan", email: "123456@example.com"}) {
username
}
}

Now query the server with the following query:

query {
users{
id
username
}
}

and you should see the new user with the username dragan. For demo purposes, data will be stored in the data folder and when entering data, they will be stored temporarily in memory. When the server is restarted, the data will be lost. To avoid this, the data should be stored in a database. In real applications, the data should be validated before being passed to the mutation. It is done through some client-side validation. Of course, the server should also validate the data.

© 2022 Dragan Vucinic