How To Implement CRUD using GraphQL, NodeJS, Express, and MongoDB — (Part Two)

Ogubuike Alexandra
8 min readApr 14, 2023

--

Hey there🙂

This article is the second part of my GraphQL series.

The first part covered the project setup and performing different kinds of read operations including fetching all resources, fetching a resource by its Id, fetching a resource/ list of resources, and nested objects within them, etc.

If you have not read it, I urge you to check it out before you continue.

As promised, in this article we will cover all you need to gain a better understanding of how to efficiently use GraphQL in your Node.js projects to implement
Create, Update, and Delete functionalities.

The code samples used in this article can be found in this GitHub repo.

In this article, we will look at:

  • Creating a root mutation
  • Creating a resource using GraphQL
  • Updating a resource using GraphQL
  • Deleting a resource using GraphQL
  • Optimizing a long-ass root mutation (from 57 lines down to 9 lines of code😏)

Let's dive into it.

In part one of this article, we created a RootQuery which served as the basis for the read operations we did.

For us to perform CUD-based operations (modify data), we need to create something called a Mutation . AMutation specifies a set of fields that represent the changes to be made to the data.

For us to get started with mutations, we will create a RootMutation in queries.js:

//File: queries.js

exports.RootMutation = new GraphQLObjectType({
name: "RootMutation",
description: "The Root Mutation",
fields: () => ({

})
})

This is going to serve as the basis for all the data manipulation we will be performing.

For RootMutation to be recognized, we need to add it to our schema in index.js :

//File: index.js

//Update imports
const { RootQuery, RootMutation } = require("./queries");

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

Great! We will start by looking at how to create a new resource.

HOW TO CREATE A RESOURCE USING GRAPHQL

Let's update our RootMutation to include code for creating an owner:

//File: queries.js

//Update our imports
const {
GraphQLObjectType,
GraphQLID,
GraphQLString,
GraphQLList,
GraphQLInt,
GraphQLNonNull //add this
} = require("graphql");

exports.RootMutation = new GraphQLObjectType({
name: "RootMutation",
description: "The Root Mutation",
fields: () => ({
addOwner: {
type: OwnerType,
description: "Adds a new owner",
args: {
name: {
type: new GraphQLNonNull(GraphQLString)
},
id: {
type: new GraphQLNonNull(GraphQLString)
}
},
resolve: async (_, args) => {
const owner = { _id : args.id, name: args.name };
return await Owner.create(owner);
},
}
})
});

Here the addOwner represents a mutation that will add a new owner to our database. The args object holds the name and _id properties which we need to create a new owner.

To make the name and _id properties required, we wrap them within GraphQLNonNull. GraphQLNonNull is a type constructor that wraps another GraphQL type and creates a new type that does not allow null values. resolve will take the arguments we provide and create a new owner object.

Let's try it out.
For a mutation, we have to append the “mutation” keyword because GraphQL automatically defaults to a query:

Let's run a query to confirm that the new owner we added is there:

Yup, there she is in all her glory! Now let's try to update our new owner.

HOW TO UPDATE A RESOURCE USING GRAPHQL

Once again, we will attack our RootMutation. This time we will add code to update an owner:

//File: queries.js

exports.RootMutation = new GraphQLObjectType({
name: "RootMutation",
description: "The Root Mutation",
fields: () => ({
addOwner: {
type: OwnerType,
description: "Adds a new owner",
args: {
name: {
type: new GraphQLNonNull(GraphQLString)
},
id: {
type: new GraphQLNonNull(GraphQLString)
}
},
resolve: async (_, args) => {
const owner = { _id: args.id, name: args.name };
return await Owner.create(owner);
},
},
updateOwner: {
type: OwnerType,
description: "Updates an owner",
args: {
name: {
type: new GraphQLNonNull(GraphQLString)
},
id: {
type: new GraphQLNonNull(GraphQLString)
}
},
resolve: async (_, args) => {
let owner = await Owner.findByIdAndUpdate(args.id, { name: args.name });

if (!owner) {
throw new Error(`Could not find owner with id: ${args.id}`);
}

return owner;
},
},
})
});

We added updateOwnerwhich represents a mutation that will update an existing owner. Notice how similar this is with addOwner.By now we should have started noticing a similar pattern😎.
DO NOT FORGET TO REFRESH BEFORE TRYING OUT THE QUERY.

Alright, let's try it out:

Test that our update worked as expected:

Before looking at the next section, I challenge you to try and add the mutation for deleting an owner by yourself🙂

The final part of our CUD operation is the delete functionality.

HOW TO DELETE A RESOURCE USING GRAPHQL

We will once again update the RootMutation to include code for deleting an owner by Id:

//File: queries.js

exports.RootMutation = new GraphQLObjectType({
name: "RootMutation",
description: "The Root Mutation",
fields: () => ({
addOwner: {
type: OwnerType,
description: "Adds a new owner",
args: {
name: {
type: new GraphQLNonNull(GraphQLString)
},
id: {
type: new GraphQLNonNull(GraphQLString)
}
},
resolve: async (_, args) => {
const owner = { _id: args.id, name: args.name };
return await Owner.create(owner);
},
},
updateOwner: {
type: OwnerType,
description: "Updates an owner",
args: {
name: {
type: new GraphQLNonNull(GraphQLString)
},
id: {
type: new GraphQLNonNull(GraphQLString)
}
},
resolve: async (_, args) => {
let owner = await Owner.findByIdAndUpdate(args.id, { name: args.name });

if (!owner) {
throw new Error(`Could not find owner with id: ${args.id}`);
}

return owner;
},
},
deleteOwner: {
type: OwnerType,
description: "Delete an owner",
args: {
id: {
type: new GraphQLNonNull(GraphQLString)
}
},
resolve: async (_, args) => {
return await Owner.findByIdAndDelete(args.id);
},
}
})
});

This is one long piece of code 😬😬.

It's the same snippet from earlier, we just added deleteOwner to handle deleting an owner.

DO NOT FORGET TO REFRESH BEFORE TRYING OUT THE QUERY.

Let's try it out:

Once again, to confirm that our delete functionality worked:

We have looked at how to CUD resources using GraphQL.
Even though this works beautifully, notice how long our RootMutation is?

By the time we add the CUD functionality for lands, the code will be super difficult to read and maintain.

Let's modularize it so that anyone who finds themselves in our codebase will not haunt us in our sleep. Remember “Clean code always!”.

Find the code for this article here

OPTIMIZING THE ROOT MUTATION FOR BETTER READABILITY

We will add the updated code inside a new file. Let's call it mutation.js.

The idea is to abstract deleteOwner , addOwner, and updateOwner into their separate functions and then pass these functions to our RootMutation.

Let's start with addOwner:

//File: mutations.js

//imports
const {
GraphQLObjectType,
GraphQLString,
GraphQLNonNull,
GraphQLInputObjectType
} = require("graphql");
const { OwnerType } = require("./queries");
const { Owner } = require("./database");

//Abstract The Input required to create an owner
const OwnerInputType = new GraphQLInputObjectType({
name: 'OwnerInput',
description: 'Fields required to create a new owner',
fields: () => ({
name: { type: new GraphQLNonNull(GraphQLString) },
_id: { type: new GraphQLNonNull(GraphQLString) }
})
});

//Abstract AddOwner functionality
const AddOwnerMutation = {
type: OwnerType,
args: {
input: { type: new GraphQLNonNull(OwnerInputType) }
},
resolve: async (_, { input }) => {
return await Owner.create(input);
}
};

//Add AddOwnerMutation to the RootMutation
exports.RootMutation = new GraphQLObjectType({
name: 'RootMutation',
description: 'The root mutation',
fields: () => ({
addOwner: AddOwnerMutation, //added this here
})
});

Here we simply abstracted the properties needed to create an owner entity by creating an GraphQLInputObjectType object. We then abstracted AddOwnerMutationand passed in the GraphQLInputObjectType object as input.
Finally, we registered AddOwnerMutation to the RootMutation .

Next, lets updateOwner :

//File: mutations.js

//Abstract UpdateOwner functionality
const updateOwner = async (id, name) => {
let owner = await Owner.findByIdAndUpdate(id, { name });

if (!owner) {
throw new Error(`Could not find owner with id: ${args.id}`);
}

return owner;
};

const UpdateOwnerMutation = {
type: OwnerType,
args: {
input: { type: new GraphQLNonNull(OwnerInputType) }
},
resolve: async (_, { input }) => {
return await updateOwner(input._id, input.name);
},
};


//Add UpdateOwnerMutation to the RootMutation
exports.RootMutation = new GraphQLObjectType({
name: 'RootMutation',
description: 'The root mutation',
fields: () => ({
addOwner: AddOwnerMutation,
updateOwner: UpdateOwnerMutation //added this here
})
});

Finally for deleteOwner :

//File: mutations.js

//Abstract DeleteOwner functionality
const DeleteOwnerMutation = {
type: OwnerType,
args: {
id: { type: new GraphQLNonNull(GraphQLString) }
},
resolve: async (_, { id }) => {
return await Owner.findByIdAndDelete(id);
}
}

//Add DeleteOwnerMutation to the RootMutation
exports.RootMutation = new GraphQLObjectType({
name: 'RootMutation',
description: 'The root mutation',
fields: () => ({
addOwner: AddOwnerMutation,
updateOwner: UpdateOwnerMutation,
deleteOwner: DeleteOwnerMutation //added this here
})
});

We have reduced RootMutation to Nine(9) lines of code and it is a beauty to behold💥

//File: mutations.js

exports.RootMutation = new GraphQLObjectType({
name: 'RootMutation',
description: 'The root mutation',
fields: () => ({
addOwner: AddOwnerMutation,
updateOwner: UpdateOwnerMutation,
deleteOwner: DeleteOwnerMutation
})
});

To make use of our optimized RootMutation , we will simply update the import statement in index.js:

//File: index.js

//Update import
const { RootQuery } = require("./queries");
const { RootMutation } = require("./mutation");

It's been a long way here and I hope you learned a lot. Thanks for reading.

Check out my other articles.

Don’t forget to stay jiggy🙂

--

--

Ogubuike Alexandra

Founder @ Codetivite | Senior Backend Engineer | Technical Writer / OpenSource Contributor @ CodeMaze