Seeding mongodb through your mongoose models

23/4/2013 Update: A change implementing promise support for the create function was just committed to GitHub, and should be available in the 3.7 release of Mongoose. Sweet!

Just wanted to share a nice little technique I discovered when working on a project using the wonderful Mongoose library to interact with a MongoDB database in Node.js. I was writing some integration tests, and needed to reset and seed the database from a set of JSON files before running each test. I really wanted to do this through my existing Mongoose models, so that I could get all the seed data validated before running my tests. Since there also are relations between my different collections I needed to insert the collections in a certain order, so my first attempt quickly devolved into a textbook example of the good ol' pyramid of doom:

User.remove({}, function(err) {  
    if(err) { return done(err); }

    Exercise.remove({}, function(err) {
        if(err) { return done(err); }

        WorkoutTemplate.remove({}, function(err) {
            if(err) { return done(err); }

            User.create(
                require('users.json'), 
                function(err) {

                if(err) { return done(err); }

                Exercise.create(
                    require('exercises.json'), 
                    function(err) {

                    if(err) { return done(err); }

                    WorkoutTemplate.create(
                        require('workoutTemplates.json'), 
                        function(err) {

                        if(err) { return done(err); }
                        else    { return done(); }
                    });
                });
            });
        });
    });
});

In the above code snippet User, Exercise, and WorkoutTemplate are my mongoose models, and since my WorkoutTemplate schema references my Exercise schema I need to insert data in that specific order. Using Mongoose's remove function with an empty object as the first argument will clear everything from the related collection. Furthermore, the create function will attempt to do a batch insert of the array you hand it. However, as you can see this quickly devolved into callback hell, and that was with just three different collections! Fortunately, I discovered that the exec function, which you can call on query objects, returns a promise object. This means I can just make a pretty, little promise chain instead of using callbacks.

There is just one obstacle to this... The create function does not return a query object, meaning we can't call exec on it to return a promise... I decided to rectify this by attaching a new function on the base mongoose Model which basically just wraps the create function, but actually returns a promise object.

var mongoose = require('mongoose');  
mongoose.Model.seed = function(entities) {  
    var promise = new mongoose.Promise;
    this.create(entities, function(err) {
        if(err) { promise.reject(err); }
        else    { promise.resolve(); }
    });
    return promise;
};

So, finally I am able to create a nice and neat promise chain in the beforeEach function of my test suite. Voilà!

beforeEach(function(done) {

    // ...

    // Reset collections
    User.remove().exec()
    .then(function() { 
        return Exercise.remove().exec() 
    })
    .then(function() { 
        return WorkoutTemplate.remove().exec() 
    })

    // Seed
    .then(function() { 
        return User.seed(
            require('users.json'));
    })
    .then(function() {
        return Exercise.seed(
            require('exercises.json'));
    })
    .then(function() {     
        return WorkoutTemplate.seed(
            require('workoutTemplates.json')); })

    // Finito!
    .then(function() { 
        done(); 
    }, function(err) { 
        return done(err); 
    });

    // ...

});

Frederik Nakstad

Read more posts by this author.

Tokyo, Japan