Using mongoose middleware to add async virtuals


In a node.js / Mongoose project, I have a schema which contains references to external image files.

var PageSchema = new Schema({
    title: String
  , media: {
        digest: String
      , name: String

Those files have additional properties which are stored in the file itself: url, width, height, exif fields, etc. Those fields will need to be populated before the model being sent to res.render().

For some fields, things are synchronous and a virtual just does the job:

PageSchema.virtual('media.url').get(function () {
    return appPaths.fileUrl(;

However, width / height, or exif fields require async calls. I thought of using middleware to populate them, but this does not seem to work:'init', function(next) {
    var media =;
    var fileName = filedb.absoluteFilePath(media);

    im.identify(fileName, function(err, features) {
        if (err) {
            media.width = 0;
            media.height = 0;
        } else {
            media.width = features.width;
            media.height = features.height;


What am I doing wrong? Is there a common design pattern for solving this kind of problem? (Other than duplicating this information in the database itself?)

Problem courtesy of: Philippe Plantier


The real problem here is that mongoose currently seems to have a wonky implementation of post callbacks. While pre('init',function(next){ ... }); works as you expect, post('init',function(next){ ... }); does not actually get passed a next function. In fact, the post init callback does not receive any arguments whatsoever when it is called.

As such, I usually write a wrapper for my query callbacks to make a sort of DIY middleware:

var setAsyncVirtuals = function(callback){
  return function(err, docs){
    if(err) return callback(err);
    var i = done = docs.length;
    if(i > 0)
          var filename = getFilename();
          im.identify(filename, function(err, features) {
            if (err) {
              docs[i].media.width = 0;
              docs[i].media.height = 0;
            } else {
              docs[i].media.width = features.width;
              docs[i].media.height = features.height;
            if(done <= 0) callback(null, docs);
        })(i); // bind i to hold value for async call
    else callback(null, docs);


Page.find({}, setAsyncVirtuals(function(err,docs){
  res.send(docs); // these have media.width & media.height assigned
Solution courtesy of: Daniel Mendel


There is currently no discussion for this recipe.

This recipe can be found in it's original form on Stack Over Flow.