Ben Wen

This article was contributed by Ben Wen

Ben works at MongoLab, a cloud hosting provider of the NoSQL MongoDB database. MongoLab monitors, backs up, and simplifies running MongoDB in production.

follow @mongolab on twitter

Object Modeling in Node.js with Mongoose

Last Updated: 28 March 2014

mongodb mongoose node

Table of Contents

MongoDB and Node.js are often used together because of their shared use of JavaScript and its object notation (JSON). JSON is quickly becoming the standard data format for web APIs and, as such, these two technologies are growing in popularity.

Mongoose is an object data modeling (ODM) library that provides a rigorous modeling environment for your data, enforcing structure as needed while still maintaining the flexibility that makes MongoDB powerful.

This article shows the use of Mongoose in a simple Node.js application on Heroku.

If you have questions about Node.js on Heroku, consider discussing it in the Node.js on Heroku forums.

Prerequisites

This article assumes you have the following:

Sample app

The sample application for this article stores and displays a collection of users.

Hello Mongoose screenshot

The source is available on GitHub at https://github.com/mongolab/hello-mongoose and can be provisioned on Heroku with a single click:

One-click app deployment.

Alternatively, you can follow the manual deployment instructions.

If you’re working with an existing application, provision a MongoDB instance from the add-ons marketplace using the Heroku CLI:

$ heroku addons:add mongolab

MongoDB connectors

There are multiple ways to connect to MongoDB from Node.js. The most popular ways are the Node.js native driver and the Mongoose.js ODM.

Native driver

The native driver's npm package is simply mongodb.

The Node.js native driver is maintained by 10gen (the makers of MongoDB) and others. It provides fundamental connectivity and data manipulation APIs and is open sourced under Apache 2.0.

The Node.js Getting Started article includes a simple example of using the driver to integrate with MongoDB.

Mongoose

Mongoose is open-sourced with the MIT license and is largely maintained by 10gen (its main contributor is employed by 10gen). It provides an object data modeling (ODM) environment that wraps the Node.js native driver.

Mongoose’s main value is that you can define schemas for your collections which are then enforced at the ODM layer by Mongoose. Mongoose.js also has utilities for simplifying Node’s callback patterns that make it easier to work with than the standard driver alone.

In general, Mongoose makes it even easier to use MongoDB with Node.js.

Initializing and connecting

Connecting to a MongoDB instance with Mongoose is straight-forward, requiring only the resource URL of the database. The sample app establishes a connection and object model with Mongoose in its app.js file.

On Heroku, resource locations are stored as environment variables, ensuring strict separation of config from code.

var http = require ('http');             // For serving a basic web page.
var mongoose = require ("mongoose"); // The reason for this demo.

// Here we find an appropriate database to connect to, defaulting to
// localhost if we don't find one.
var uristring =
process.env.MONGOLAB_URI ||
process.env.MONGOHQ_URL ||
'mongodb://localhost/HelloMongoose';

// The http server will listen to an appropriate port, or default to
// port 5000.
var theport = process.env.PORT || 5000;

// Makes connection asynchronously.  Mongoose will queue up database
// operations and release them when the connection is complete.
mongoose.connect(uristring, function (err, res) {
  if (err) {
  console.log ('ERROR connecting to: ' + uristring + '. ' + err);
  } else {
  console.log ('Succeeded connected to: ' + uristring);
  }
});

The connection to MongoDB is made in mongoose.connect. Mongoose helps hide some complexity in this step, waiting for the connection to be made before sending data. In contrast, the Node.js native driver requires handling the rest of your program in its callback.

Object modeling

MongoDB has a flexible schema that allows for variability between different documents in the same collection. That flexibility can be very powerful, for example, as a database’s uses evolve over time. But that flexibility doesn’t mean that you need to forgo some safety checks like type and range checking.

Model a user by specifying its properties in a mongoose schema.

var userSchema = new mongoose.Schema({
  name: {
first: String,
last: { type: String, trim: true }
  },
  age: { type: Number, min: 0}
});

This is a simple Mongoose schema for a User object. It has a name composed of a last and first as Strings, and an age that’s limited to be no less than zero. The last name is trimmed of extraneous leading and trailing whitespace. These validations are enforced when new objects are created and, if they fail, an error is raised and can be caught by your code.

To instantiate a new user, create a Mongoose model from the schema in the PowerUsers collection and populated with a specific user’s details.

var PUser = mongoose.model('PowerUsers', userSchema);
...
// Creating one user.
var johndoe = new PUser ({
  name: { first: 'John', last: '  Doe   ' },
  age: 25
});

// Saving it to the database.
johndoe.save(function (err) {if (err) console.log ('Error on save!')});

MongoDB creates databases and collections on demand if they do not already exist.

A sample user, johndoe is created with an age of 25, and then is saved to the MongoDB database.

Querying

Fetching the list of power users from the collection is done with the capable find method. The query syntax for Mongoose is a chain of conditions to be matched. The query is executed by invoking the .exec method with a callback that is run when the query is fulfilled asynchronously.

Below is the generic form for the query. Other query conditions can be applied to filter specific fields, sort and paginate.

PUser.find({}).exec(function(err, result) {
  if (!err) {
    // handle result
  } else {
    // error handling
  };
});

In the application’s code, two queries are run to demonstrate slightly different approaches. The first query is the empty object which matches against all documents and is immediately executed. The second query describes a name.last match on ‘Doe’ and then adds another constraint on age to be greater than 64.

Below the query is the empty object which matches against all documents. To query based on conditions use the query syntax for Mongoose which is a chain of properties to be matched, typically ending in .exec() with a callback that is invoked when the query is fulfilled asynchronously. This is a very flexible way to construct a query that is only executed when needed.

function createWebpage (req, res) {
  // Let's find all the documents
  PUser.find({}).exec(function(err, result) {
if (!err) {
  res.write(html1 + JSON.stringify(result, undefined, 2) +  html2 + result.length + html3);
  // Let's see if there are any senior citizens (older than 64) with the last name Doe using the query constructor
  var query = PUser.find({'name.last': 'Doe'}); // (ok in this example, it's all entries)
  query.where('age').gt(64);
  query.exec(function(err, result) {
    if (!err) {
      res.end(html4 + JSON.stringify(result, undefined, 2) + html5 + result.length + html6);
    } else {
      res.end('Error in second query. ' + err)
    }
  });
} else {
  res.end('Error in first query. ' + err)
};
  });
}

Model metadata

You may have noticed here or when you first connected to the demo application through your browser, that there were two new fields added to the document. These fields are _id and __v (two underscores). The _id is an ObjectID, that is 12-byte binary value assigned by MongoDB to uniquely identify the document in a collection. You may override its assignment with your own identification scheme, so long as the uniqueness property holds for that collection.

The __v field is the version key and is added by Mongoose and is used to track revisions of a document.

Introspection

To examine the data in the MongoLab UI, go to your app’s resource list in the Heroku Dashboard and click on the MongoLab icon. This will use single-sign-on to authenticate you to the MongoLab interface where you can click on the PowerUsers collection and see an editable web UI view of the document.

Mongolab dashboard screenshot

For larger collections, the saved queries can also be very helpful in debugging and developing an application.

Summary

In this tutorial you created a Node.js application with Mongoose that connects to the MongoLab add-on in Heroku. For next steps you can visit the links below for more information about each component.

MongoDB, Mongoose and Node.js all use JSON. Because of this, these technologies can help you build modern backend APIs, mobile services, and dynamic web applications more easily.