Introducing Mongoose

While working directly with the mongodb module is great, it's also a bit raw and lacks any sense of developer friendliness that we've come to expect working with frameworks such as Express in Node.js. Mongoose is a great third-party framework that makes working with MongoDB a breeze. Mongoose is an elegant mongodb object modeling for Node.js.

What that basically means is that Mongoose gives us the power to organize our database by using schemas (also known as model definitions) and providing powerful features to our models such as validation, virtual properties, and more. Mongoose is a great tool as it makes working with collections and documents in MongoDB feel much more elegant. The original mongodb module is a dependency of Mongoose, so you can think of Mongoose as being a wrapper on top of mongodb much like Express is a wrapper on top of Node.js—both abstract away a lot of the "raw" feeling and give you easier tools to work with directly.

It's important to note that Mongoose is still MongoDB, so everything you're familiar with and used to will work pretty much the same way; only the syntax will change slightly. This means that the queries and inserts and updates that we know and love from MongoDB work perfectly fine with Mongoose.

Let's take a look at some of the features that Mongoose has to offer and what we'll take advantage of to make our lives easier when developing apps that heavily rely on a MongoDB database.

Schemas

In Mongoose, schemas are what we use to define our models. Think of them as a blueprint that all models you create throughout the app will derive from. Using schemas, you can define much more than the simple blueprint of a MongoDB model. You can also take advantage of the built-in validation that Mongoose provides by default, add static methods, virtual properties, and more!

The first thing we do while defining a schema for a model is build a list of every field we think we will need for a particular document. The fields are defined by type, and the standard datatypes you would expect are available as well as a few others:

  • String
  • Number
  • Date
  • Buffer
  • Boolean
  • Mixed: "Anything goes" field type. Consider this when storing JSON type data or data that is arbitrary and can literally be any JSON representation. It doesn't need to be predefined.
  • ObjectId: Typically used when you want to store the ObjectID of another document in a field, for example when defining a relationships.
  • Array: A collection of other schemas (that is, models).

Here is an example of a basic Mongoose Schema definition:

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

var Account = new Schema({
    username: { type: String },
    date_created: { type: Date, default: Date.now },
    visits: { type: Number, default: 0 },
    active: { type: Boolean, default: false }
});

Here we define our schema for an Accounts collection. The first thing we do is require mongoose and then define a schema object using mongoose.Schema in our module. We define a schema by creating a new Schema instance with a constructor object that defines the schema. Each field in the definition is a basic JavaScript object with type, and then an optional default value.

Models

A model in Mongoose is a class that can be instantiated (defined by a schema). Using schemas, we define models and then use them like a regular JavaScript object. The benefit is that the model object has the added bonus of being backed by Mongoose, so it also includes features such as saving, finding, creating, and removing. Let's take a look at defining a model using a schema and then instantiating a model and working with it.

The first thing we need to do is install Mongoose so that it's available to use within our mongotest project:

$ npm install mongoose

Continuing with editing our experimentation file, mongotest/test.js, include the following block of code after the existing code:

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

mongoose.connect('mongodb://localhost:27017/mongotest'),
mongoose.connection.on('open', function() {
    console.log('Mongoose connected.'),
});

var Account = new Schema({
    username: { type: String },
    date_created: { type: Date, default: Date.now },
    visits: { type: Number, default: 0 },
    active: { type: Boolean, default: false }
});

var AccountModel = mongoose.model('Account', Account);
var newUser = new AccountModel({  username: 'randomUser' });
console.log(newUser.username);
console.log(newUser.date_created);
console.log(newUser.visits);
console.log(newUser.active);

Running the preceding code should result in something similar to the following:

$ node test.js
randomUser
Mon Jun 02 2014 13:23:28 GMT-0400 (EDT)
0
false

Creating a new model is great when you're working with new documents and you want a way to create a new instance, populate its values, and then save it to the database:

var AccountModel = mongoose.model('Account', Account);
var newUser = new AccountModel({  username: 'randomUser' });
newUser.save();

Calling .save on a Mongoose model will trigger a command to MongoDB that will perform the necessary insert or update statements to update the server. When you switch over to your mongo shell, you can see the new user was indeed saved to the database:

> use mongotest
switched to db mongotest
> db.accounts.find()
{ "username" : "randomUser", "_id" : ObjectId("538cb4cafa7c430000070f66"), "active" : false, "visits" : 0, "date_created" : ISODate("2014-06-02T17:30:50.330Z"), "__v" : 0 }

Note that without calling .save() on the model, the changes to the model won't actually be persisted to the database. Working with Mongoose models in your node code is just that—code. You have to execute MongoDB functions on a model for any actual communication to occur with the database server.

You can use the AccountModel to perform a find operation and return an array of AccountModel objects based on some search criteria that retrieve results from the MongoDB database:

// assuming our collection has the following 4 records:
// { username: 'randomUser1', age: 21 }
// { username: 'randomUser2', age: 25 }
// { username: 'randomUser3', age: 18 }
// { username: 'randomUser4', age: 32 }

AccountModel.find({ age: { $gt : 18, $lt : 30} }, function(err, accounts){
    console.log(accounts.length);    // => 2
    console.log(accounts[0].username);    // => randomUser1
    mongoose.connection.close();
});

Here we use the standard MongoDB $gt and $lt for the value of age when passing in our query parameter to find (that is, find any document where the age is above 18 and below 30). The callback function that executes after find references an accounts array, which is a collection of AccountModel objects returned from the query to MongoDB. As a general means of good housekeeping, we close the connection to the MongoDB server after we are finished.

Built-in validation

One of the core concepts of Mongoose is that it enforces a schema on top of a schema-less design such as MongoDB. In doing so, we gain a number of new features, including built-in validation. By default, every schema type has a built-in required validator available. Furthermore, numbers have both min and max validators and strings have enumeration and matching validators. Custom validators can also be defined via your schemas. Let's take a brief look at some validation added to our example schema from earlier:

var Account = new Schema({
    username: { type: String, required: true },
    date_created: { type: Date, default: Date.now },
    visits: { type: Number, default: 0 },
    active: { type: Boolean, default: false },
    age: { type: Number, required: true, min: 13, max: 120 }
});

The validation we added to our schema is that the username parameter is now required, and we included a new field called age, which is a number that must be between 13 and 120 (years). If either value doesn't match the validation requirements (that is username is blank or age is less than 13 or greater than 120), an error will be thrown.

Validation will fire automatically whenever a model's .save() function is called; however, you can also manually validate by calling a model's .validate() function with a callback to handle the response. Building on the example, add the following code that will create a new mongoose model from the schema defined:

var AccountModel = mongoose.model('Account', Account);
var newUser = new AccountModel({  username: 'randomUser', age: 11 });
newUser.validate(function(err) {
    console.log(err);
});
// the same error would occur if we executed:
// newUser.save();

Running the preceding code should log the following error to the screen:

{ message: 'Validation failed',
  name: 'ValidationError',
  errors:
   { age:
      { message: 'Path 'age' (11) is less than minimum allowed value (13).',
        name: 'ValidatorError',
        path: 'age',
        type: 'min',
        value: 11 } } }

You can see that the error object that is returned from validate is pretty useful and provides a lot of information that can help when validating your model and returning helpful error messages back to the user.

Validation is a very good example of why it's so important to always accept an error object as the first parameter to any callback function in Node. It's equally important that you check the error object and handle appropriately.

Static methods

Schemas are flexible enough so that you can easily add your own custom static methods to them, which then become available to all of your models that are defined by that schema. Static methods are great to add helper utilities and functions that you know you're going to want to use with most of your models. Let's take our simple age query from earlier and refactor it so that it's a static method and a little more flexible:

var Account = new Schema({
    username: { type: String },
    date_created: { type: Date, default: Date.now },
    visits: { type: Number, default: 0 },
    active: { type: Boolean, default: false },
    age: { type: Number, required: true, min: 13, max: 120 }
});

Account.statics.findByAgeRange = function(min, max, callback) {
    this.find({ age: { $gt : min, $lte : max} }, callback);
};
var AccountModel = mongoose.model('Account', Account);

AccountModel.findByAgeRange(18, 30, function(err, accounts){
    console.log(accounts.length);    // => 2
});

Static methods are pretty easy to implement and will make your models much more powerful once you start taking full advantage of them!

Virtual properties

Virtual properties are exactly what they sound like—fake properties that don't actually exist in your MongoDB documents, but you can fake them by combining other real properties. The most obvious example of a virtual property would be a field for full name, when only the first and last name are actual fields in the MongoDB collection. For the full name, you simply want to say, "return the model's first and last name combined as a single string and label it fullname":

// assuming the Account schema has firstname and lastname defined:

Account.virtual('fullname')
    .get(function() {
          return this.firstname + ' ' + this.lastname;
    })
    .set(function(fullname) {
        var parts = fullname.split(' '),
        this.firstname = parts[0];
        this.lastname = parts[1];
    });

Using the virtual function of a schema, we provide the name of the property as a string. Then, we call the .get() and .set() functions. It's not required to provide both, although it's fairly common. Sometimes, it may be impossible to provide .set() functionalities based on the nature of .get().

In this example, our get() function simply performs basic string concatenation and returns a new value. Our .set() function performs the reverse—splitting a string on a space and assigning the models firstname and lastname field values with each result. You can see that the .set() implementation is a little flakey if someone attempts to set a model's fullname with a value of say, Dr. Kenneth Noisewater.

Tip

It's important to note that virtual properties are not persisted to MongoDB since they are not real fields in the document or collection.

There's a lot more you can do with Mongoose, and we only just barely scratched the surface. Fortunately, it has a fairly in-depth guide you can refer to at the following link:

http://mongoosejs.com/docs/guide.html

Definitely spend some time reviewing the Mongoose documentation so that you are familiar with all of the powerful tools and options available.

That concludes our introduction to Mongoose's models, schemas, and validation. Next up, let's dive back into our main application and write the schemas and models that we will be using to replace our existing sample viewModels as well as connecting with Mongoose.

Connecting with Mongoose

The act of connecting to a MongoDB server with Mongoose is almost identical to the method we used earlier when we used the mongodb module.

First, we need to ensure that Mongoose is installed. At this point, we are going to be using Mongoose in our main app, so we want to install it in the main project directory and also update the package.json file. Using your command-line terminal program, change locations to your project folder, and install Mongoose via npm, making sure to use the --save flag so that the package.json file is updated:

$ cd ~/projects/imgPloadr
$ npm install mongoose --save

With Mongoose installed and the package.json file updated for the project, we're ready to open a connection to our MongoDB server. For our app, we are going to open a connection to the MongoDB server once the app itself boots up and maintain an open connection to the database server for the duration of the app's lifetime. Let's edit the server.js file to include the connection code we need. First, include Mongoose in the app by requiring it at the very top of the file:

var express = require('express'),
    config = require('./server/configure'),
    app = express(),
    mongoose = require('mongoose'),

Then, insert the following code right after the app = config(app); line:

mongoose.connect('mongodb://localhost/imgPloadr'),
mongoose.connection.on('open', function() {
    console.log('Mongoose connected.'),
});

That's it! Those few simple lines of code are all it takes to open a connection to a MongoDB server, and our app is ready to start communicating with the database. The only parameter we pass to the connect function of mongoose is a URL string to our locally running MongoDB server and a path to the collection we want to use. Then, we add an event listener to the 'open' event of the mongoose.connection object and when that fires, we simply log an output message that the database server has connected.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.29.151