Combining mongoose and Q in node.js

This post will teach you how to write promise-based mongoose code, using Kris Kowal’s Q library.

You can also find the code below in the `src/mongoose-and-q` dir of my blog code repository.

We have a mongo database with 3 collections: users, bicycles and cars. Bicycles and cars are owned by users. We will write a script that shows the bicycles and cars owned by rupert@example.com .

We’ll look at multiple versions of this script. This article focuses on callbacks and promises, so we’ll be using a hardcoded email address and we’ll not be handling errors very well. Those aspects of the code are for a different article.

The first version uses callbacks for the database calls.

'use strict';

var mongoose = require('mongoose');
var Schema   = mongoose.Schema;

var UserSchema = new Schema({
  email    : { type: String },
  firstName: { type: String },
});
var User = mongoose.model('User', UserSchema);

var CarSchema = new Schema({
  ownerId       : { type: Schema.Types.ObjectId, ref: 'User' },
  colour        : { type: String },
  numberOfWheels: { type: Number },
});
var Car = mongoose.model('Car', CarSchema);

var BicycleSchema = new Schema({
  ownerId       : { type: Schema.Types.ObjectId, ref: 'User' },
  colour        : { type: String },
  numberOfWheels: { type: Number },
});
var Bicycle = mongoose.model('Bicycle', BicycleSchema);

mongoose.connect('mongodb://localhost/blogtest');

var email = 'rupert@example.com';

function getVehicles(email, cb) {
  User.findOne({ email: email}, function(err, user) {
    if (err) {
      return cb(err);
    }
    Car.find({ ownerId: user._id }, function(err, cars) {
      if (err) {
        return cb(err);
      }
      Bicycle.find({ ownerId: user._id }, function(err, bicycles) {
        if (err) {
          return cb(err);
        }
        cb(null, {
          cars: cars,
          bicycles: bicycles
        });
      });
    });
  });
}

getVehicles(email, function(err, vehicles) {
  if (err) {
    console.error('Something went wrong: ' + err);
  }
  else {
    console.info(vehicles);
  }
  mongoose.disconnect();
});

Two things stand out in this code. First, the indenting in getVehicles . It looks bad. It is bad. And if we want to add more callbacks, it gets worse. Second, we are using error handling code in each individual callback.

We can rewrite this code to use Q .

'use strict';

var Q        = require('q');
var mongoose = require('mongoose');
var Schema   = mongoose.Schema;

var UserSchema = new Schema({
  email    : { type: String },
  firstName: { type: String },
});
var User = mongoose.model('User', UserSchema);

var CarSchema = new Schema({
  ownerId       : { type: Schema.Types.ObjectId, ref: 'User' },
  colour        : { type: String },
  numberOfWheels: { type: Number },
});
var Car = mongoose.model('Car', CarSchema);

var BicycleSchema = new Schema({
  ownerId       : { type: Schema.Types.ObjectId, ref: 'User' },
  colour        : { type: String },
  numberOfWheels: { type: Number },
});
var Bicycle = mongoose.model('Bicycle', BicycleSchema);

mongoose.connect('mongodb://localhost/blogtest');

var email = 'rupert@example.com';

function getVehicles(email) {
  var foundCars, foundUser;
  return Q(User.findOne({ email: email }).exec())
  .then(function(user) {
    foundUser = user;
    return Q(Car.find({ ownerId: user._id }).exec())
  })
  .then(function(cars) {
    foundCars = cars;
    return Q(Bicycle.find({ ownerId: foundUser._id }).exec())
  })
  .then(function(bicycles) {
    return {
      bicycles: bicycles,
      cars: foundCars
    };
  });
}

getVehicles(email)
.then(function(vehicles) {
  console.log(vehicles);
})
.catch(function(err) {
  console.error('Something went wrong: ' + err);
})
.done(function() {
  mongoose.disconnect();
});

The getVehicles  function now returns the promise created on line 33. The user is found, then the cars, then the bicycles, and then the promise resolves to the same object as in the first listing.

This version of getVehicles looks better regarding both indenting and error handling. The indenting stays on the same level, and the error handling – no matter where the error happens – is done by the catch  block after calling getVehicles .

However, there is also a disadvantage to this code. The vars from a then  block can’t be used in the next then  block, because each consecutive then  block has its own function scope. That’s why we have to assign user  and cars  to helper vars outside their scope.

We can do better. Let’s look at our improved version of getVehicles .

function getVehicles(email) {
  return Q(User.findOne({ email: email }).exec())
  .then(function(user) {
    return Q.all([
      Q(Bicycle.find({ ownerId: user._id }).exec()),
      Q(Car.find({ ownerId: user._id }).exec())
    ]);
  })
  .then(function(results) {
    return {
      bicycles: results[0],
      cars: results[1]
    };
  });
}

We use Q.all(arrayOfPromises) to retrieve both the cars  and the bicycles  in one go. Q.all  resolves to a list of fulfilment values, which end up in an array in the then  block.

This both makes the code shorter and gets rid of the helper variables. There is one minor improvement that we can still make.

function getVehicles(email) {
  return Q(User.findOne({ email: email }).exec())
  .then(function(user) {
    return [
      Q(Bicycle.find({ ownerId: user._id }).exec()),
      Q(Car.find({ ownerId: user._id }).exec())
    ];
  })
  .spread(function(bicycles, cars) {
    return {
      bicycles: bicycles,
      cars: cars
    };
  });
}

We replace .then  by .spread , so the bicycles  and cars  end up in one argument each, instead of an array of results. Additionally, because .spread  calls .all  initially, we can now remove Q.all and return just the array of promises. This code is more readable and maintainable than the previous version.

This entry was posted in JavaScript, node.js, software development and tagged , , , , , , , , , . Bookmark the permalink.

4 Responses to Combining mongoose and Q in node.js

Leave a Reply

Your email address will not be published. Required fields are marked *