This article was contributed by a member of the Heroku community
Its contents might not always reflect updates to the Heroku platform.
Object Modeling in Node.js with Mongoose
Last updated 20 February 2019
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.
Prerequisites
This article assumes you have the following:
- A Heroku account. Signup is free and instant.
- The Heroku CLI installed
Sample app
The sample application for this article stores and displays a collection of users.
The source is available on GitHub at https://github.com/mongolab/hello-mongoose and can be provisioned on Heroku with a single click:
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:create 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 MongoDB, Inc. (the makers of MongoDB) and others. It provides fundamental connectivity and data manipulation APIs and is open sourced under Apache 2.0.
Mongoose
Mongoose is open-sourced with the MIT license and is also maintained by MongoDB, Inc. 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; however, we advise against doing so.
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.
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.