The most comprehensive article on Medium for CRUD operations in MongoDB (using Mocha, Mongoose and Nodemon)

Akhil Sharma
Design and Tech.Co
Published in
11 min readOct 16, 2018

Let’s say you want to create a complete CRUD functionality for MongoDB. Now, the thing is, there are multiple operations happening here and if you’re doing it for the first time or performing the same operations multiple times in your application (assuming you have multiple CRUDs in your application), chances are you are bound to go wrong in multiple places.

MongoDB CRUD

NodeJS, being the non-blocking server it is, will execute all requests at once and spit out multiple errors, making it a highly painful task to pinpoint the exact place where the error occurs, because the flow doesn’t break at one place, everything executes simultaneously!

And this is why you cannot setup your development environment like your production environment. It is a great idea to not have your development environment work as a non-blocking server and to execute a line, check results and then proceed to the next operation, thereby spitting the right error at the right place, making it easy to fix issues coming up in development. And at the same time, you definitely want your production environment to be non-blocking so that the operation doesn’t get delayed anywhere, even if there are errors.

For the development environment, we use Mocha, a library that helps us test every single operation using something called as “assertions” and returning values through “promises” that can be “chained” in a series to perform almost magical operations. Mocha gives us a time limit of 2000ms (milliseconds) before a value is returned, if the value isn’t returned and the assertion doesn’t return true, it spits an error at this exact place, making it easy for you to pinpoint the problem area. Sounds interesting right? (the next steps in this article assumes that you have basic knowledge about mongoDB, mongoose, NodeJS and nodemon)

Let’s get started, connect to your mongoDB database using the code below (keep this in a file called test_helper.js and keep it in test folder) ->

const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/user_test');
mongoose.connection
.once('open', ()=> console.log('Good to Go'!'))
.on('error', (error)=> {
console.warn('warning',error);
});

Here, user_test is your database. So, if you have multiple databases, user_test is the database you want to connect to (replace it with your own database).

Now, the second step, as you know if creating a schema using mongoose. Code below (put this in user.js file in src folder) —

const mongoose = require('mongoose');
const schema = mongoose.Schema;
const UserSchema = new Schema ({
name:String
});
const User = mongoose.model('user', UserSchema);
module.exports = User;

So, we have now created a schema which will have one property -> ‘name’ of type ‘string’

‘mongoose.model’ checks whether mongoDB has a collection called “user” or not, and if it doesn’t it automatically creates it for you. It’s also telling mongoDB that the collection “user” need to follow the schema “UserSchema”. Please note that “user” (from const user) is not an object but actually a class and represents all the users in the collection. ‘module.exports’ simply gives access of this module to any file in the entire project.

Now, create a new file, create_test.js to create a file where we can keep all our test cases and write the code below -

const assert = require('assert');
const User = require('../src/user');
decribe('Creating records', () => {
it('saves a user', () =>{
const joe = new User({name: 'Joe'});
joe.save(); });
});

Now, let’s discuss what the code above does for you. Basically, mocha follows a format of ‘describe’ and ‘it’ blocks. You have to describe what you are doing, for example “creating records” in our case and you define various conditions with ‘it’ blocks, in our case, we are creating a new user called “joe” and then we execute the joe.save() command which saves the user in mongoDB. There’s a gotcha here — you may think that when you created a user named “joe”, he automatically got saved in the database, because that’s how all other ORMs like Eloquent and ActiveRecord work right? well, things are a bit different here, you actually have to explicitly tell mongoDB to save the user, just declaring doesn’t mean saving. All clear?

So, we have a test case setup to check whenever a new user is added (we are not actually “checking” and returning any value if the user is added, we will do that using assertions, shortly. But since we have used the ‘describe’ and ‘it’ blocks, let’s assume for now that the test is working). You will soon realise that you cannot actually run this test in the real world, because, every single time you run it, there is actually a new user that gets added, with the name Joe! In our case, we wanted to create an isolation and see whether this line of code actually works, we’re instead encountering a problem where more and more users are getting added of the same name! To counter this, we can devise a method to delete all the users in our collection before each time the test runs. We can achieve this by using “before each” command. Please modify your “test_helper.js” file as shown below (all the new updates in the code are in bold) ->

const mongoose = require('mongoose');
mongoose.Promise = global.Promise;
before((done) => {

mongoose.connect('mongodb://localhost/user_test');
mongoose.connection
.once('open', ()=> {done();})
.on('error', (error)=> {
console.warn('warning',error);
});
});beforeEach((done) => {
mongoose.connection.collection.users.drop();
done();
});

What we’re doing is this — before each time the test runs, we drop all the collections in the database. You will also notice that I have wrapped the mongoose.connect function in a ‘before’ command, we’re doing this to ensure that the connection is established before any of the test cases are executed, as otherwise there is no point of executing any of the test cases as all of them will fail ! You will notice a strange thing called ‘done()’ in the above code, this is simply a function that tells the server that this operation is now “done” and you can go ahead with the rest of the code. If you remember, we have already talked about this in the beginning of the section, this helps to identify and pin point the exact problem area, if the server executes a command and doesn’t arrive at the ‘done()’ statement after 2000ms, it spits an error at that stage. It is important to note ‘global.Promise’ which tells mongoose to use ES6 promises otherwise you will get a “deprecated” error.

Now that you know about ‘done()’, we can proceed to learn about “assertions”. Assertions are basically the conditions with the help of which we can check whether a test case has passed or failed. If you view the code on the “create_test.js” file, you may notice the first line is ‘require(‘assert’);’ Here’s how they work (make the below changes in your create_test.js file)-

const assert = require('assert');
const User = require('../src/user');
decribe('Creating records', () => {
it('saves a user', (done) =>{
const joe = new User({name: 'Joe'});
joe.save()
.then(()=>{
assert(!joe.isNew));
}); });
});

The above code will now check if joe is a new entry (we check this by using ‘isNew’ function), if it is not a new entry (!joe.isNew), that means joe was saved successfully and if it is a new entry, the assertion will fail and so will the test case, an error will be displayed. We have successfully checked and verified if operations are being performed quickly, if they are being performed, we pass ‘done’ which tells the server to proceed with the rest of the code.

Now that we have so many errors, let’s use nodemon, which is a live server to fix these issues in real time. This will automate our testing. Go to your package.json file and in place of the line that has a test package, write this ->

"test": "nodemon --exec 'mocha -R min'"

This combined nodemon with mocha and ‘min’ helps you minify the errors in a clean format while displaying them to you. A lot of tutorials will teach you to use the mocha ‘watch’ library to look for errors, but it’s just slow and unreliable and thus, nodemon is the correct way to go.

We’re at a good place, we have learnt how to setup a great test environment, how to save records in mongoDB using mongoose and also check whether they have been saved using assertions. It’s time to take a look at reading records from mongoDB.

Create a new file called ‘’reading_test.js’’ and paste the code below -

const User = require('../src/user');describe('Reading users out of the database', () => {
let joe;
beforeEach((done) => {
joe = new User({name:'Joe'});
joe.save()
.then(() => done());
});
it('finds all users with a name of joe', () => {
User.find({name: 'Joe' })
.then((users) => {
console.log(users[0]._id);
assert(users[0]._id === joe._id);
done();
});
});

The code starts with the beforeEach function which ensures that a record named ‘Joe’ is there in the database before we proceed to read a record with the name ‘Joe’ else there will be an unnecessary error. In the ‘it’ block, we have run a query to find users with the name ‘joe’, we can use either User.find or User.findOne which are both relevant but User.findOne will return just one particular value and User.find will return an array -> this is a very important feature and you need to handle the data coming from both these functions differently. Since, we are using User.find and will be receiving an array, in the next line of code, we are displaying the result and then in the line next to that, we are asserting whether the id of the user we have just found is the same of the id of ‘Joe’. This is a beautiful piece of code and should work flawlessly without any problem. But here’s the twist — it doesn’t work !

The issue is that mongoDB, while it shows us that the id of both joes’ is same, the ternary operator is forcing it to check the value of the id in its memory and not the visible value assigned to it. To solve this issue, we need to use the .toString() operator. Modify the assert line from the above example like so -

assert(users[0]._id.toString() === joe._id.toString());

So, you’ve learnt how to read a record using Find function, let’s see how to use findOne. Add the below ‘it’ block to the code in the previous example -

it('find a user with a particular id', (done) => {
User.findOne({_id: joe._id})
.then((user) =>{
assert(user.name === 'Joe');
done();
});
});
});

The code above will help you find a user by using an id and then you can check whether his name is ‘Joe’ or not.

Let’s look at how to delete users, we have done C (create), R (read), now it’s time to D (delete) in CRUD. Create a new file called ‘delete_test.js’ and paste the following code —

const assert = require('assert');
const User = requre('../src/user');
describe('Deleting a user', () => {
let joe;
beforeEach((done) => {
joe = new User({name:'Joe'});
joe.save()
.then(() => done());
});
});
it('model instance remove', (done) => {
joe.remove()
.then(() => User.findOne({ name: 'Joe' }))
.then ((user) => {
assert(user === null);
done();
});
it('class method remove', (done) => {User.remove({name:'joe'})
.then(() => User.findOne({name: 'Joe'}))
.then((user) => {
assert(user === null);
done();
});
});it('class method findOneAndRemove', (done) => {

User.findOneAndRemove({name: 'Joe' })
.then(() => User.findOne({name: 'Joe'}))
.then((user) => {
assert(user === null);
done();
});
it('class method findByIdAndRemove', (done) => {User.findByIdAndRemove(joe._id)

.then(() => User.findOne({name: 'Joe'}))
.then((user) => {
assert(user === null);
done();
});
});
});

We have used four types of delete operations here (these are all that mongoDB provides) ->

  1. Model instance remove
  2. Class method remove
  3. Class method findOneAndRemove
  4. Class method findByIdAndRemove

The names of the functions are quite self explanatory, basically joe.remove() deletes an instance of joe, which means it deletes only one particular record, whereas, User.remove(name: ‘Joe’) removes all instances of that class and this means it will delete all records with name ‘Joe’ and this is the main difference between the first two delete operations. The 3rd one finds the first instance it finds with name ‘Joe’ and deletes it and the 4th one deletes the instance of joe with a particular id that we have specified.

In all of these 4 delete operations, you must have noticed nesting of the ‘then’ promise. This is called as ‘Promise Chaining’ and is a great concept. Here’s how it works, you run ‘joe.remove()’ and that removes an instance with the name of joe, then you tell mongoose to find a record with name=’joe’ (using the first ‘then promise) and then take you’re asking mongoose to take the same record that it has now found (‘using the second ‘then’ promise) and check if his value is ‘null’ by using an assertion and it should be ‘null’ because it has been deleted and this helps us verify whether the records have been deleted or now. Really interesting use case of promise chaining right?

It’s now time to look at Update (U). MongoDB provides us with 5 different ways to update a record -

  1. Update instance using set and save
  2. Model instance update
  3. Class update
  4. FindOneAndUpdate
  5. FindByIdAndUpdate

Create a new file called “update_test.js” and write the following code -

const assert = require('assert');
const User = require('../src/user');
describe('updating records', () => {
let joe;
beforeEach((done) => {
joe = new User({ name: 'Joe' });
joe.save()
.then(() => done());
});
it('instance type using set n save', () => {joe.set('name', 'Alex');
joe.save()
.then(() => User.find({})
.then ((users) => {
assert(users.length === 1);
assert(users[0].name === 'Alex');
done();
});
});

This uses the first update operation provided to us -> the set and save method for instance. We set joe’s name to alex by using ‘joe.set(‘name’, ‘Alex’) and saving this by ‘joe.save()’ , the next line, which is a ‘.then’ promise finds all the users where the above update has been made and checks whether the name is now ‘alex’. We will be using the ‘.then’ assertions repeatedly for all the 5 cases, so let’s create a function called ‘assertName’ and store it instead of having to type it again and again and exposing ourselves to unnecessary errors.

function assertName(operation, done) {.then(() => User.find({}) 
.then ((users) => {
assert(users.length === 1);
assert(users[0].name === 'Alex');
done();
}

and the way to chain this newly created function with you update query (shown in the previous example) is as below -

assertName(joe.save(), done);

Let’s look at our second update, the model instance update -

it('A model instance can update, (done)) => {
assertName(joe.update ({name: 'Alex' }), done);
)};
)};

This bulk updates all instances with name ‘Joe’ to ‘Alex’ in one go and doesn’t follow the set and save way.

Let’s now look at the remaining 3 ways (3rd, 4th and 5th in the list). Paste the code below (the three ‘it’ blocks) in the same file -

it ('A model class can update', (done) => {
assertName(
User.update({ name: 'Joe' }, { name: 'Alex' }),
done
);
});
it ('A model class can update one record', (done) => {
assertName(
User.findOneAndUpdate({name: 'Joe'}, {name: 'Alex'}),
done
);
});it('A model class can find a record with an Id and update'. (done) => {
assertname(
User.findByIdAndUpdate(joe._id, {name: 'Alex'}),
done
);
});

The 3rd one works very similar to the delete class operation, it simply replaces all instances of joe with alex. The 4th one finds one particular instance, basically the first one that it can find and then replaces the name with alex. The 5th one uses an id and then updates the name to alex (very similar to the delete using id).

We have looked at the complete CRUD in a very comprehensive way and seen 4 ways of deleting and 5 ways of updating, we also saw how to use nodemon with mocha. We learnt how to use assertions, promise chaining, creating functions for promise chaining and so on. Hope you learnt a lot from this lesson and are not super confident in MongoDB. I’ll be tackling deeper and more complex topics in later articles. Do follow me for awesome content like this.

We at Myrl Tech (www.myrl.tech) build some of the most complex digital products for our clients using cutting edge technologies, do get in touch with us if you have a requirement :)

Follow Here

--

--

Akhil Sharma
Design and Tech.Co

Founder at https://www/armur.ai. Web3 Engineer (Substrate, Solana, Hyperledger, Cosmos, Ethereum)