Handling data in MEAN.io with Mongoose.js

Brace yourselves, long post coming, but it matters. MEAN.io is a fully functioning app right out of the box, it amounts to a platform which allows user authentication and authoring articles which then appear in an index. It all works miraculously well, a second after you’ve installed it and booted your Node server, but what are the odds that it’s the app that you need? Virtually none, of course. You can, though, with limited experience alter the app to suit other needs. For this you need to understand what happens to data from its collection from the user by Angular on the client-side to its storage in MongoDB. This, I think, is actually the coolest part of the MEAN stack. You can push the exact same Javascript object from the client to the database without any difficulties or translations into other languages. The missing link which greatly facilitates this process is a beast called Mongoose.js.

Mongoose

Mongoose brands itself as an “elegant mongodb object modeling for node.js“. It is what lets you quite easily from a node app connect to a Mongo database, model and define your data objects, store and find them in the db and much, much more. For this reason Mongoose is what the authors of MEAN.io rightfully so chose to use.

This is how it works:

MEAN  is  MVC on the client-side and MVC on the server side, so it has models, its models on the server-side are mongoose models, and for that before they are created, mongoose is needed so it brought into the module with Node’s require:

var mongoose = require('mongoose')

In server.js then mongoose is used to connect to the MongoDB defined in the db part of the global config

// Bootstrap db connection
var db = mongoose.connect(config.db)

The config specifies mongodb as the type of database we are using, localhost as the server and mean-dev as the database we are saving to. This reads with great brevity as follows

db: 'mongodb://localhost/mean-dev',

Then here in /app/models/article.js a Schema variable is declared as a Mongoose schema.

Schema = mongoose.Schema;

 

Then we are ready to model our data object:

var ArticleSchema = new Schema({
	created: {type : Date, default : Date.now},
	title: {type: String, default: '', trim : true},
        content: {type: String, default: '', trim : true},
	user: {type : Schema.ObjectId, ref : 'User'}
});

This is quite self explanatory, but in case you are unfamiliar. We are instantiating a new mongoose schema object so that we inherit all of its handy methods and each of the new object’s custom new fields is defined with another object so that we give that field a definition – is it a string, is it a date, is it an id, does it have a default and what is it, should white space be trimmed etc. All of these amounts to something you may be used to doing when starting your SQL database, but here the schema is defined in the actual code logic, not in the database itself. While it is similar to SQL schemas,  this is much more flexible as schemas in SQL are notoriously cumbersome hard to change and often have to cause down time for the whole software application.

The user: {type : Schema.ObjectId, ref : ‘User’} is a bit more peculiar and amounts to the MongoDB way of handling joins. It tells mongoose that the user field in the Article model is an id which matches a document in the User collection.

Mongoose schemas go further though and can actually define methods to your data objects as well. These are called statics. From the Mongoose doc – Schemas not only define the structure of your document and casting of properties, they also define document instance methods, static Model methods, compound indexes and document lifecycle hooks called middleware.

This explains the following load method for the Article model defined in the schema.

ArticleSchema.statics = {
  load: function (id, cb) {
    this.findOne({ _id : id }).populate('user').exec(cb);
  }
};

Remember this load static method as it plays an important role in loading the data.

We are now ready to create the actual model.

mongoose.model('Article', ArticleSchema);

Within mongoose a model by the name of Article as defined by the ArticleSchema is created, very easy right  It is very important to note that unless set otherwise in the schema, mongoose will use the name of the model as name of the collection in your MongoDB server. So Article models will be stored in the articles collection. 

We can verify this with Robomongo, a neat client for Mongo:

Screen Shot 2013-08-31 at 11.44.02 PM

This model is then bootstrapped globally for this app in the server.js file – the one Node actually runs to start the whole app.

// Bootstrap models
var models_path = __dirname + '/app/models'
fs.readdirSync(models_path).forEach(function (file) {
  require(models_path+'/'+file)
})

Here the fs.readdirSync method is used to return a list of the files in the /app/models folder and a forEach loop requires them all into the global memory for this app.

Now the models are ready to be used by the controllers which are in charge of the server side logic.If you look at the articles controller you will notice it makes use of both the Article and User models.

var User = mongoose.model('User')
var Article = mongoose.model('Article')

Now if you recall previously we look at the definition of the load static method. The load method accepts an id and a callback, finds an article by id and then executes the callback.

This load static method is used and a callback is passed in the controllers/articles and looks like this:

exports.article = function(req, res, next, id){
  var User = mongoose.model('User')

  Article.load(id, function (err, article) {
    if (err) return next(err)
    if (!article) return next(new Error('Failed to load article ' + id))
    req.article = article
    next()
  })
}

This article function from the articles controller is actually used in router.js with app.param which maps a route parameter to an actual article. An amazingly cool feature of express which is explained here.

app.param('articleId', articles.article)

This articleId param is then used by the get, put, del requests to the articles controller.

app.get('/articles/:articleId', articles.show)
app.put('/articles/:articleId', auth.requiresLogin, auth.article.hasAuthorization, articles.update)
app.del('/articles/:articleId', auth.requiresLogin, auth.article.hasAuthorization, articles.destroy)

If you look at the handler functions like for example articles.show, you will see that req.article holds the article data, thanks to the mapping of the load method to the articleId param. So you can just go ahead and just return it with jsonp as a response. Again, very cool, very convenient.

exports.show = function(req, res){
    res.jsonp(req.article);
}

The delete request calls the destroy function which calls the remove method of the article model we’re working with and wipes it out from the db.

exports.destroy = function(req, res){
  var article = req.article
  article.remove(function(err){
    if (err) {
		res.render('error', {status: 500});
	} else {			
		res.jsonp(article);
	}
  })
}

The post and put requests call create and update accordingly and use the save method to either store or update an article, based on whether a callback with data was passed or not.

/**
 * Create a article
 */
exports.create = function (req, res) {
  var article = new Article(req.body)
  article.user = req.user
  article.save()
  res.jsonp(article)
}

/**
 * Update a article
 */
exports.update = function(req, res){
  var article = req.article
  article = _.extend(article, req.body)

  article.save(function(err) {
  	res.jsonp(article)
  })
}

And there you have it, we have analyzed in detail the role which Mongoose.js plays in mean.io – it connects to the database, defines the models, loads them, creates, reads, update and deletes them from the db which means that it essentially handles the whole connectivity between MongoDB and Express – the server-side of your app. Next we will analyze how this data is handled on the client-side by Angular.

3 thoughts on “Handling data in MEAN.io with Mongoose.js

  1. vonwolf

    July 3, 2014 at 4:41pm

    Very good article on mongoose and making it simple to understand!

    Yet there is one question which could be addressed : how can you use inside the angular controllers the schema you have defined on node side so that you don’t have to rewrite it.

    • Took

      August 16, 2014 at 1:43pm

      iAgree

  2. UX Dork

    November 25, 2014 at 6:27pm

    Thank you for this! I’m building my app with the MEAN stack currently and this was helpful :)

    Can you explain more what _.extend is doing in exports.update? That’s how I came to this article from Google.

Comments are closed.