On Tuesday I did a presentation for the Pittsburgh JavaScript Developers Meetup group about using Node.js with NoSQL – or document based – databases. I chose MongoDB because it’s got great traction and there’s a Node module for it named Mongoose.

Unfortunately, I was on vacation the week before the presentation, so I didn’t get to cover absolutely everything that I wanted to, but I was able to show some great highlights of what can be done with Mongoose. When I first started to look in to it, I was immediately attracted to its syntax and API being very JavaScript friendly.

First things first, though. You can install Mongoose easily by using npm – npm install mongoose.

Then you simply need to include it and create a reference to a local database (if it doesn’t exist, Mongoose will create it for you).

var mongoose = require('mongoose'),
    db = mongoose.connect('mongodb://localhost/test');

Now you’re ready to start working with your database.

One of my favorite things about this system is that you can define, transform, create, and delete all sorts of information without an active database connection. That’s because MongoDB is document based instead of table based.

In the Mongoose API, it all starts with Schemas – something not needed in base MongoDB. A schema is the definition of your document – a rough concept of a table in RDBMS – but instead of all the normalization that goes on in relational systems, documents get embedded in other documents instead of being joined together.

Let’s look at my sample. First, I create an instance of mongoose.Schema and then I create two document schemas – Beer and Type.

var mongoose = require('mongoose'),
    db = mongoose.connect('mongodb://localhost/test'),
    Schema = mongoose.Schema,
    
    /**
     *  This is the beer type document that is embedded in the beer document
     */
    Type = new Schema({
        name            : String,
        main_ingredient : String
    }),

    /**
     *  This is the beer document which has a sub-document of Type
     */
    Beer = new Schema({
        brand       : String,
        type        : [Type],   // Embed Type into Beer (kinda like join)
        brewery_age : Number,
        rating      : Number
    });

Now that we’ve defined what a beer is, we make a model for it.

BeerModel = mongoose.model('Beer', Beer);

Now that we have a model for beer, we can create instances. Notice that the parameter is simply a JSON object with each property defined… except the Type.

  var Yuengling = new BeerModel({ 
    brand:'Yuengling', 
    brewery_age:105, 
    rating: 40 
  });

To assign properties to a sub-document you use this syntax.

  Yuengling.type.push({ 
    name:'Lager', 
    main_ingredient:'Malted Barley' 
  });

Ok, now that we’ve defined our first beer, let’s save it to the database. No INSERT INTO needed here, it’s absurdly simple. You call the save() method; much more JavaScript friendly syntax. It’s how you would write a save method on any object. Like always in Node – because we never want to block the I/O on our main event loop – you pass an anonymous function that will receive an error message if something went wrong.

Yuengling.save(function(err){ 
    if (err) { console.log(err); } 
  });

Let’s assume, for brevity’s sake – that I’ve added 5 more beers to the database. How would you query it to find beers that have a rating higher than, say, 35? Perhaps the designers of MongoDB live by the K.I.S.S. principle, because you use the find() method. However, the syntax for using operands in the argument list is a bit counter-intuitive.

Let’s find those on our BeerModel.

BeerModel.find({ rating: {$gt: 35} }, function(err, beers) {
   beers.forEach(function(beer){
     console.log(beer);
   });
});

Another feature I really like in Mongoose is you can add methods directly on the schema and then call those methods on any instance. They serve the purpose of a stored procedure in the relational world.

I’m going to modify Beer and create two methods that allow me to increment and decrement the value of it rating property.

    Beer = new Schema({
        brand       : String,
        type        : [Type],   // Embed Type into Beer (kinda like join)
        brewery_age : Number,
        rating      : Number
    }).method('increaseRating', function(){
      this.rating += 1;
      return this.rating;
    }).method('decreaseRating', function(){
      this.rating -= 1;
      return this.rating;
    });

   /**
    * Increase rating of Yuengling
    */
   Yuengling.increaseRating();

   /**
    * Decrese rating of Yuengling
    */
   Yuengling.decreaseRating();

How about a validation trigger? In this case, when you call the save() method with a rating that doesn’t validate, the err argument to the callback function will contain an exception with the message “Rating for beer is not between 10 and 50″.

/**
 * Validate the rating field to be between 10 and 50
 */
Beer.path('rating').validate(function(val){
  return val>10 && val<=50;
}, 'Rating for beer is not between 10 and 50');

These are just scratching the surface of what you can do. At this point in time, the Mongoose API is a bit sparsely documented, but still, if you want to go through the full API you can find everything you need.

You can see my full code on my Github account.