I was using mongoosejs for connecting mongoDB from my NodeJS app. When an action involve mulitple queries it tend to be callback hell.
I didn’t used populate
method for loading related data to demonstate the promises.
I start with connecting mongoose with the mongoDB.
// connection.js
var mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/my_db');
var db = mongoose.connection;
db.on('error', console.error.bind(console, 'Connection Error : '));
db.once('open', function(){
console.log('Connection ok!');
});
module.exports = mongoose;
And mongoose models looks like
// User model
// models/user.js
var mongoose = require('../connection');
var Schema = mongoose.Schema;
var ObjectId = Schema.ObjectId;
var UserSchema = new Schema({
email: String,
access_token: String,
name: String,
username: { type: String,required: true, index: { unique: true, sparse: true }}
});
//Project model
// models/project.js
var mongoose = require('../connection');
var Schema = mongoose.Schema;
var ObjectId = Schema.ObjectId;
var ProjectSchema = new Schema({
name: String,
user_id: {type: ObjectId, ref: 'User'},
});
//Issue model
// models/issue.js
var mongoose = require('../connection');
var Schema = mongoose.Schema;
var ObjectId = Schema.ObjectId;
var IssueSchema = new Schema({
title: String,
body: String,
project_id: {type: ObjectId, ref: 'Project'},
});
So when I need to list all the issues of a project I need to fetch the User then the Project and the list issues of that project. Let the route for the action like /:username/:project/issues
. So Initially my action code looks like,
var User = require('./models/user');
var Project = require('./models/project');
var Issue = require('./models/issue');
exports.index = function (req, res) {
var username = req.params.username;
var project = req.params.project;
User.findOne({username: username}, function(err, user){
if(err) {
console.log(err);
return
}
Project.findOne({name: project, user: user._id}, function(err, project){
if(err) {
console.log(err);
return
}
Issues.find({project_id: project._id}, function(err, issues){
if(err) {
console.log(err);
return
}
res.render('./views/issues/index', {user: user, project: poject, issues: issues});
})
});
});
}
But this code looks difficult to maintain for me. I usually uses promises to avoid callback hell. So I thought of using promises with mongoosejs since they have inbuilt support for it. So I rewrote my code with promises
var User = require('./models/user');
var Project = require('./models/project');
var Issue = require('./models/issue');
exports.index = function (req, res) {
var username = req.params.username;
var project = req.params.project;
User.findOne({username: username}).exec()
.then(function(user){
var result = [];
return Project.findOne({name: project, user_id: user._id}).exec()
.then(function(project){
return [user, project];
});
})
.then(function(result){
var project = result[1];
return Issues.find({project_id: project._id}).exec()
.then(function(issues) {
result.push(issues);
return result;
})
})
.then(function(result){
var user = result[0];
var project = result[1];
var issues = result[2];
res.render('./views/issues/index', {user: user, project: project, issues: issues});
})
.then(undefined, function(err){
//Handle error
})
}
Have you noticed the use of exec()
method? In mongoose, exec method will execute the query and return a Promise. We can make this code even better using populate
method, Since I am learning this is far better than before and let the populate
method be subject for another blog post. ;)
Comments are welcome.