MongoDB is one of the most popular document databases in the NodeJS world. It constantly evolves and has huge community support. As a result, developers created various Object Document Mapper (ODM) tools like Mongoose, Mongolia, Waterline, etc. These ODM tools provide high level abstractions and make our lives a lot easier while working with MongoDB. In this blog post, we are going to discuss Mongoose. We assume that you have basic knowledge about this tool; if not, you can go through the "quick start guide" and detailed documentation at http://mongoosejs.com
Here's a scenario: We have a student and a course and we want to enroll the student into the course. But before enrolling the student, we need to make sure that there are enough seats available in the course. If we follow the official Mongoose documentation then our code should look like this:
var Student = require('./student.model');
var Course = rqurie('./course.model');
//Let's assume we are using Express framework for nodeJs
function enrollStudent(req, res, next) {
//First we need to make sure that student exists and load the user.
Student.findById(req.body.studentId, function(err, student){
if(err) return next(err);
//Now we need to load the course
Course.findById(req.body.courseId, function(err, course){
if(err) return next(err);
//Next we need to check if there are available seats in the course
if(course.isSeatAvailable()){
course.enrolledStudents.push(student.id);
course.save(function(err){
return res.json({message : 'Enrollment successful'});
});
} else {
return next({message : 'No seats available'});
}
});
});
}
As you can see, even in this simple scenario our function is expanding horizontally because of callbacks. More complicated scenarios will have more callbacks and eventually the code becomes unreadable. One way to solve this problem is by using "promises." If you are not familiar with Promises spec, you can learn more about it at http://promisesaplus.com. In Mongoose 4.x, all queries have an exec() method that returns a promise object. Model.save() method also returns a promise. Let's rewrite our function using promises...
var student;
//Load the user
Student.findById(req.body.studentId).exec()
//Capture student and load the course
.then(function(studentFromDb){
student = studentFromDb
return Course.findById(req.body.courseId).exec();
})
//Check if there are available seats in the course
.then(function(course){
if(course.isSeatAvailable()){
//Enroll student into the course
course.enrolledStudents.push(student.id);
return course;
} else {
//throw an error
throw new Error('No seats available');
}
})
//Save the course
.then(function(course){
return course.save();
})
//Catch all errors and call the error handler;
.then(null, next);
}
Promise chaining allows us to avoid the "callback hell," and the promise catch block eliminates extraneous error checks. By comparing these two examples, you can see that using promises in Mongoose will make your code a lot cleaner and more readable.