Mongoose is a robust Node.js ODM module that adds MongoDB support to your Express application. Mongoose uses schemas to model your entities, offers predefined validation along with custom validations, allows you to define virtual attributes, and uses middleware hooks to intercept operations. The Mongoose design goal is to bridge the gap between the MongoDB schemaless approach and the requirements of real-world application development. In this Lesson, you'll go through the following basic features of Mongoose:
Mongoose is a Node.js module that provides developers with the ability to model objects and save them as MongoDB documents. While MongoDB is a schemaless database, Mongoose offers you the opportunity to enjoy both strict and loose schema approaches when dealing with Mongoose models. Like with any other Node.js module, before you can start using it in your application, you will first need to install it. The examples in this Lesson will continue directly from those in the previous Lessons; so for this Lesson, copy the final example from Lesson 2, Building an Express Web Application, and let's start from there.
Once you've installed and verified that your MongoDB local instance is running, you'll be able connect it using the Mongoose module. First, you will need to install Mongoose in your application modules folders, so change your package.json
file to look like the following code snippet:
{
"name": "MEAN",
"version": "0.0.5",
"dependencies": {
"express": "~4.8.8",
"morgan": "~1.3.0",
"compression": "~1.0.11",
"body-parser": "~1.8.0",
"method-override": "~2.2.0",
"express-session": "~1.7.6",
"ejs": "~1.0.0",
"mongoose": "~3.8.15"
}
}
To install your application dependencies, go to your application folder and issue the following command in your command-line tool:
$ npm install
This will install the latest version of Mongoose in your node_modules
folder. After the installation process has successfully finished, the next step will be to connect to your MongoDB instance.
To connect to MongoDB, you will need to use the MongoDB connection URI. The MongoDB connection URI is a string URL that tells the MongoDB drivers how to connect to the database instance. The MongoDB URI is usually constructed as follows:
mongodb://username:password@hostname:port/database
Since you're connecting to a local instance, you can skip the username and password and use the following URI:
mongodb://localhost/mean-book
The simplest thing to do is define this connection URI directly in your config/express.js
configuration file and use the Mongoose module to connect to the database as follows:
var uri = 'mongodb://localhost/mean-book'; var db = require('mongoose').connect(uri);
However, since you're building a real application, saving the URI directly in the config/express.js
file is a bad practice. The proper way to store application variables is to use your enviornment configuration file. Go to your config/env/development.js
file and change it to look like the following code snippet:
module.exports = {
db: 'mongodb://localhost/mean-book',
sessionSecret: 'developmentSessionSecret'
};
Now in your config
folder, create a new file named mongoose.js
that contains the following code snippet:
var config = require('./config'),
mongoose = require('mongoose');
module.exports = function() {
var db = mongoose.connect(config.db);
return db;
};
Notice how you required the Mongoose module and connected to the MongoDB instance using the db
property of your configuration object. To initialize your Mongoose configuration, go back to your server.js
file, and change it to look like the following code snippet:
process.env.NODE_ENV = process.env.NODE_ENV || 'development'; var mongoose = require('./config/mongoose'), express = require('./config/express'); var db = mongoose(); var app = express(); app.listen(3000); module.exports = app; console.log('Server running at http://localhost:3000/');
That's it, you have installed Mongoose, updated your configuration file, and connected to your MongoDB instance. To start your application, use your command-line tool, and navigate to your application folder to execute the following command:
$ node server
Your application should be running and connected to the MongoDB local instance.
Connecting to your MongoDB instance was the first step but the real magic of the Mongoose module is the ability to define a document schema. As you already know, MongoDB uses collections to store multiple documents, which aren't required to have the same structure. However, when dealing with objects, it is sometime necessary for documents to be similar. Mongoose uses a Schema
object to define the document list of properties, each with its own type and constraints, to enforce the document structure. After specifying a schema, you will go on to define a Model
constructor that you'll use to create instances of MongoDB documents. In this section, you'll learn how to define a user schema and model, and how to use a model instance to create, retrieve, and update user documents.
To create your first schema, go to the app/models
folder and create a new file named user.server.model.js
. In this file, paste the following lines of code:
var mongoose = require('mongoose'), Schema = mongoose.Schema; var UserSchema = new Schema({ firstName: String, lastName: String, email: String, username: String, password: String }); mongoose.model('User', UserSchema);
In the preceding code snippet, you did two things: first, you defined your UserSchema
object using the Schema
constructor, and then you used the schema instance to define your User
model. Next, you'll learn how to use the User
model to perform CRUD operations in your application's logic layer.
Before you can start using the User
model, you will need to include the user.server.model.js
file in your Mongoose configuration file in order to register the User
model. To do so, change your config/mongoose.js
file to look like the following code snippet:
var config = require('./config'),
mongoose = require('mongoose');
module.exports = function() {
var db = mongoose.connect(config.db);
require('../app/models/user.server.model');
return db;
};
Make sure that your Mongoose configuration file is loaded before any other configuration in the server.js
file. This is important since any module that is loaded after this module will be able to use the User
model without loading it by itself.
You can start using the User
model right away, but to keep things organized, it is better that you create a Users
controller that will handle all user-related operations. Under the app/controllers
folder, create a new file named users.server.controller.js
and paste the following lines of code:
var User = require('mongoose').model('User'); exports.create = function(req, res, next) { var user = new User(req.body); user.save(function(err) { if (err) { return next(err); } else { res.json(user); } }); };
Let's go over this code. First, you used the Mongoose module to call the model method that will return the User
model you previously defined. Next, you create a controller method named create()
, which you will later use to create new users. Using the new
keyword, the create()
method creates a new model instance, which is populated using the request body. Finally, you call the model instance's save()
method that either saves the user and outputs the user
object, or fail, passing the error to the next middleware.
To test your new controller, let's add a set of user-related routes that call the controller's methods. Begin by creating a file named users.server.routes.js
inside the app/routes
folder. In this newly created file, paste the following lines of code:
var users = require('../../app/controllers/users.server.controller'); module.exports = function(app) { app.route('/users').post(users.create); };
Since your Express application will serve mainly as a RESTful API for the AngularJS application, it is a best practice to build your routes according to the REST principles. In this case, the proper way to create a new user is to use an HTTP POST
request to the base users route as you defined here. Change your config/express.js
file to look like the following code snippet:
var config = require('./config'),
express = require('express'),
morgan = require('morgan'),
compress = require('compression'),
bodyParser = require('body-parser'),
methodOverride = require('method-override'),
session = require('express-session');
module.exports = function() {
var app = express();
if (process.env.NODE_ENV === 'development') {
app.use(morgan('dev'));
} else if (process.env.NODE_ENV === 'production') {
app.use(compress());
}
app.use(bodyParser.urlencoded({
extended: true
}));
app.use(bodyParser.json());
app.use(methodOverride());
app.use(session({
saveUninitialized: true,
resave: true,
secret: config.sessionSecret
}));
app.set('views', './app/views');
app.set('view engine', 'ejs');
require('../app/routes/index.server.routes.js')(app);
require('../app/routes/users.server.routes.js')(app);
app.use(express.static('./public'));
return app;
};
That's it! To test it out, go to your root application folder and execute the following command:
$ node server
Your application should be running. To create a new user, perform an HTTP POST
request to the base users route, and make sure the request body includes the following JSON:
{ "firstName": "First", "lastName": "Last", "email": "[email protected]", "username": "username", "password": "password" }
Another way to test your application would be to execute the following curl
command in your command-line tool:
$ curl -X POST -H "Content-Type: application/json" -d '{"firstName":"First", "lastName":"Last","email":"[email protected]","username":"username","password":"password"}' localhost:3000/users
The find()
method is a model method that retrieves multiple documents stored in the same collection using a query and is a Mongoose implementation of the MongoDB find()
collection method. To understand this better, add the following list()
method in your app/controllers/users.server.controller.js
file:
exports.list = function(req, res, next) { User.find({}, function(err, users) { if (err) { return next(err); } else { res.json(users); } }); };
Notice how the new list()
method uses the find()
method to retrieve an array of all the documents in the users
collection. To use the new method you created, you'll need to register a route for it, so go to your app/routes/users.server.routes.js
file and change it to look like the following code snippet:
var users = require('../../app/controllers/users.server.controller');
module.exports = function(app) {
app.route('/users')
.post(users.create)
.get(users.list);
};
All you have left to do is run your application by executing the following command:
$ node server
Then, you will be able to retrieve a list of your users by visiting http://localhost:3000/users
in your browser.
In the preceding code example, the find()
method accept two arguments, a MongoDB query object and a callback function, but it can accept up to four parameters:
For instance, to retrieve only the usernames and e-mails of your users, you would modify your call to look like the following lines of code:
User.find({}, 'username email', function(err, users) { ... });
Furthermore, you can also pass an options object when calling the find()
method, which will manipulate the query result. For instance, to paginate through the users
collection and retrieve only a subset of your users
collection, you can use the skip
and limit
options as follows:
User.find({}, 'username email', { skip: 10, limit: 10 }, function(err, users) { ... });
This will return a subset of up to 10 user documents while skipping the first 10 documents.
To learn more about query options, it is recommended that you visit Mongoose official documentation at http://mongoosejs.com/docs/api.html.
Retrieving a single user document is done using the findOne()
method, which is very similar to the find()
method, but retrieves only the first document of the subset. To start working with a single user document, we'll have to add two new methods. Add the following lines of code at the end of your app/controllers/users.server.controller.js
file:
exports.read = function(req, res) { res.json(req.user); }; exports.userByID = function(req, res, next, id) { User.findOne({ _id: id }, function(err, user) { if (err) { return next(err); } else { req.user = user; next(); } }); };
The read()
method is simple to understand; it is just responding with a JSON representation of the req.user
object, but what is creating the req.user
object? Well, the userById()
method is the one responsible for populating the req.user
object. You will use the userById()
method as a middleware to deal with the manipulation of single documents when performing read, delete, and update operations. To do so, you will have to modify your app/routes/users.server.routes.js
file to look like the following lines of code:
var users = require('../../app/controllers/users.server.controller'); module.exports = function(app) { app.route('/users') .post(users.create) .get(users.list); app.route('/users/:userId') .get(users.read); app.param('userId', users.userByID); };
Notice how you added the users.read()
method with a request path containing userId
. In Express, adding a colon before a substring in a route definition means that this substring will be handled as a request parameter. To handle the population of the req.user
object, you use the app.param()
method that defines a middleware to be executed before any other middleware that uses that parameter. Here, the users.userById()
method will be executed before any other middleware registered with the userId
parameter, which in this case is the users.read()
middleware. This design pattern is useful when building a RESTful API, where you often add request parameters to the routing string.
To test it out, run your application using the following command:
$ node server
Then, navigate to http://localhost:3000/users
in your browser, grab one of your users' _id
values, and navigate to http://localhost:3000/users/[id]
, replacing the [id]
part with the user's _id
value.
The Mongoose model has several available methods to update an existing document. Among those are the update()
, findOneAndUpdate()
, and findByIdAndUpdate()
methods. Each of the methods serves a different level of abstraction, easing the update operation when possible. In our case, and since we already use the userById()
middleware, the easiest way to update an existing document would be to use the findByIdAndUpdate()
method. To do so, go back to your app/controllers/users.server.controller.js
file, and add a new update()
method:
exports.update = function(req, res, next) { User.findByIdAndUpdate(req.user.id, req.body, function(err, user) { if (err) { return next(err); } else { res.json(user); } }); };
Notice how you used the user's id
field to find and update the correct document. The next thing you should do is wire your new update()
method in your users' routing module. Go back to your app/routes/users.server.routes.js
file and change it to look like the following code snippet:
var users = require('../../app/controllers/users.server.controller');
module.exports = function(app) {
app.route('/users')
.post(users.create)
.get(users.list);
app.route('/users/:userId')
.get(users.read)
.put(users.update);
app.param('userId', users.userByID);
};
Notice how you used the route you previously created and just chained the update()
method using the route's put()
method. To test your update()
method, run your application using the following command:
$ node server
Then, use your favorite REST tool to issue a PUT
request, or use curl
and execute this command, replacing the [id]
part with a real document's _id
property:
$ curl -X PUT -H "Content-Type: application/json" -d '{"lastName": "Updated"}' localhost:3000/users/[id]
The Mongoose model has several available methods to remove an existing document. Among those are the remove()
, findOneAndRemove()
, and findByIdAndRemove()
methods. In our case, and since we already use the userById()
middleware, the easiest way to remove an existing document would be to simply use the remove()
method. To do so, go back to your app/controllers/users.server.controller.js
file, and add the following delete()
method:
exports.delete = function(req, res, next) { req.user.remove(function(err) { if (err) { return next(err); } else { res.json(req.user); } }) };
Notice how you use the user
object to remove the correct document. The next thing you should do is use your new delete()
method in your users' routing file. Go to your app/routes/users.server.routes.js
file and change it to look like the following code snippet:
var users = require('../../app/controllers/users.server.controller');
module.exports = function(app) {
app.route('/users')
.post(users.create)
.get(users.list);
app.route('/users/:userId')
.get(users.read)
.put(users.update)
.delete(users.delete);
app.param('userId', users.userByID);
};
Notice how you used the route you previously created and just chained the delete()
method using the route's delete()
method. To test your delete
method, run your application using the following command:
$ node server
Then, use your favorite REST tool to issue a DELETE
request, or use curl
and execute the following command, replacing the [id]
part with a real document's _id
property:
$ curl -X DELETE localhost:3000/users/[id]
This completes the implementation of the four CRUD operations, giving you a brief understanding of the Mongoose model capabilities. However, these methods are just examples of the vast features included with Mongoose. In the next section, you'll learn how to define default values, power your schema fields with modifiers, and validate your data.
Performing data manipulations is great, but to develop complex applications, you will need your ODM module to do more. Luckily, Mongoose supports various other features that help you safely model your documents and keep your data consistent.
Defining default field values is a common feature for data modeling frameworks. You can add this functionality directly in your application's logic layer, but that would be messy and is generally a bad practice. Mongoose offers to define default values at the schema level, helping you organize your code better and guarantee your documents' validity.
Let's say you want to add a created
date field to your UserSchema
. The created
date field should be initialized at creation time and save the time the user document was initially created; a perfect example of when you can utilize a default value. To do so, you'll have to change your UserSchema
, so go back to your app/models/user.server.model.js
file and change it to look like the following code snippet:
var mongoose = require('mongoose'), Schema = mongoose.Schema; var UserSchema = new Schema({ firstName: String, lastName: String, email: String, username: String, password: String, created: { type: Date, default: Date.now } }); mongoose.model('User', UserSchema);
Notice how the created
field is added and its default value defined. From now on, every new user document will be created with a default creation date that represents the moment the document was created. You should also notice that every user document created prior to this schema change will be assigned a created
field representing the moment you queried for it, since these documents don't have the created
field initialized.
To test your new changes, run your application using the following command:
$ node server
Then, use your favorite REST tool to issue a POST
request or use cURL, and execute the following command:
$ curl -X POST -H "Content-Type: application/json" -d '{"firstName":"First", "lastName":"Last","email":"[email protected]","username":"username","password":"password"}' localhost:3000/users
A new user document will be created with a default created field initialized at the moment of creation.
Sometimes, you may want to perform a manipulation over schema fields before saving them or presenting them to the client. For this purpose, Mongoose uses a feature called modifiers. A modifier can either change the field's value before saving the document or represent it differently at query time.
The simplest modifiers are the predefined ones included with Mongoose. For instance, string-type fields can have a trim modifier to remove whitespaces, an uppercase modifier to uppercase the field value, and so on. To understand how predefined modifiers work, let's make sure the username of your users is clear from a leading and trailing whitespace. To do so, all you have to do is change your app/models/user.server.model.js
file to look like the following code snippet:
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
var UserSchema = new Schema({
firstName: String,
lastName: String,
email: String,
username: {
type: String,
trim: true
},
password: String,
created: {
type: Date,
default: Date.now
}
});
mongoose.model('User', UserSchema);
Notice the trim
property added to the username
field. This will make sure your username data will be kept trimmed.
Predefined modifiers are great, but you can also define your own custom setter modifiers to handle data manipulation before saving the document. To understand this better, let's add a new website field to your User
model. The website
field should begin with 'http://'
or 'https://'
, but instead of forcing your customer to add this in the UI, you can simply write a custom modifier that validates the existence of these prefixes and adds them when necessary. To add your custom modifier, you will need to create the new website
field with a set
property as follows:
var UserSchema = new Schema({ ... website: { type: String, set: function(url) { if (!url) { return url; } else { if (url.indexOf('http://') !== 0 && url.indexOf('https://') !== 0) { url = 'http://' + url; } return url; } } }, ... });
Now, every user created will have a properly formed website URL that is modified at creation time. But what if you already have a big collection of user documents? You can of course migrate your existing data, but when dealing with big datasets, it would have a serious performance impact, so you can simply use getter modifiers.
Getter modifiers are used to modify existing data before outputting the documents to next layer. For instance, in our previous example, a getter modifier would sometimes be better to change already existing user documents by modifying their website
field at query time instead of going over your MongoDB collection and updating each document. To do so, all you have to do is change your UserSchema
like the following code snippet:
var UserSchema = new Schema({ ... website: { type: String, get: function(url) { if (!url) { return url; } else { if (url.indexOf('http://') !== 0 && url.indexOf('https://') !== 0) { url = 'http://' + url; } return url; } } }, ... }); UserSchema.set('toJSON', { getters: true });
You simply changed the setter modifier to a getter modifier by changing the set
property to get
. But the important thing to notice here is how you configured your schema using UserSchema.set()
. This will force Mongoose to include getters when converting the MongoDB document to a JSON representation and will allow the output of documents using res.json()
to include the getter's behavior. If you didn't include this, you would have your document's JSON representation ignoring the getter modifiers.
Modifiers are powerful and can save you a lot of time, but they should be used with caution to prevent unpredicted application behavior. It is recommended you visit http://mongoosejs.com/docs/api.html for more information.
Sometimes you may want to have dynamically calculated document properties, which are not really presented in the document. These properties are called virtual attributes and can be used to address several common requirements. For instance, let's say you want to add a new fullName
field, which will represent the concatenation of the user's first and last names. To do so, you will have to use the virtual()
schema method, so a modified UserSchema
would include the following code snippet:
UserSchema.virtual('fullName').get(function() { return this.firstName + ' ' + this.lastName; }); UserSchema.set('toJSON', { getters: true, virtuals: true });
In the preceding code example, you added a virtual attribute named fullName
to your UserSchema
, added a getter
method to that virtual attribute, and then configured your schema to include virtual attributes when converting the MongoDB document to a JSON representation.
But virtual attributes can also have setters to help you save your documents as you prefer instead of just adding more field attributes. In this case, let's say you wanted to break an input's fullName
field into your first and last name fields. To do so, a modified virtual declaration would look like the following code snippet:
UserSchema.virtual('fullName').get(function() { return this.firstName + ' ' + this.lastName; }).set(function(fullName) { var splitName = fullName.split(' '); this.firstName = splitName[0] || ''; this.lastName = splitName[1] || ''; });
Virtual attributes are a great feature of Mongoose, allowing you to modify document representation as they're being moved through your application's layers without getting persisted to MongoDB.
As we previously discussed, MongoDB supports various types of indexes to optimize query execution. Mongoose also supports the indexing functionality and even allows you to define secondary indexes.
The basic example of indexing is the unique
index, which validates the uniqueness of a document field across a collection. In our example, it is common to keep usernames unique, so in order to tell that to MongoDB, you will need to modify your UserSchema
definition to include the following code snippet:
var UserSchema = new Schema({
...
username: {
type: String,
trim: true,
unique: true
},
...
});
This will tell MongoDB to create a unique index for the username field of the users
collections. Mongoose also supports the creation of secondary indexes using the index
property. So, if you know that your application will use a lot of queries involving the email
field, you could optimize these queries by creating an e-mail secondary index as follows:
var UserSchema = new Schema({ ... email: { type: String, index: true }, ... });
Indexing is a wonderful feature of MongoDB, but you should keep in mind that it might cause you some trouble. For example, if you define a unique
index on a collection where data is already stored, you might encounter some errors while running your application until you fix the issues with your collection data. Another common issue is Mongoose's automatic creation of indexes when the application starts, a feature that could cause major performance issues when running in a production environment.
Mongoose models are pretty packed with both static and instance predefined methods, some of which you already used before. However, Mongoose also lets you define your own custom methods to empower your models, giving you a modular tool to separate your application logic properly. Let's go over the proper way of defining these methods.
Model static methods give you the liberty to perform model-level operations, such as adding extra find
methods. For instance, let's say you want to search users by their username. You could of course define this method in your controller, but that wouldn't be the right place for it. What you're looking for is a static model method. To add a static method, you will need to declare it as a member of your schema's statics
property. In our case, adding a findOneByUsername()
method would look like the following code snippet:
UserSchema.statics.findOneByUsername = function (username, callback) { this.findOne({ username: new RegExp(username, 'i') }, callback); };
This method is using the model's findOne()
method to retrieve a user's document that has a certain username. Using the new findOneByUsername()
method would be similar to using a standard static method by calling it directly from the User
model as follows:
User.findOneByUsername('username', function(err, user){ ... });
You can of course come up with many other static methods; you'll probably need them when developing your application, so don't be afraid to add them.
Static methods are great, but what if you need methods that perform instance operations? Well, Mongoose offers support for those too, helping you slim down your code base and properly reuse your application code. To add an instance method, you will need to declare it as a member of your schema's methods
property. Let's say you want to validate your user's password with an authenticate()
method. Adding this method would then be similar to the following code snippet:
UserSchema.methods.authenticate = function(password) { return this.password === password; };
This will allow you to call the authenticate()
method from any User
model instance as follows:
user.authenticate('password');
As you can see, defining custom model methods is a great way to keep your project properly organized while making reuse of common code. In the upcoming Lessons, you'll discover how both the instance and static methods can be very useful.
One of the major issues when dealing with data marshaling is validation. When users input information to your application, you'll often have to validate that information before passing it on to MongoDB. While you can validate your data at the logic layer of your application, it is more useful to do it at the model level. Luckily, Mongoose supports both simple predefined validators and more complex custom validators. Validators are defined at the field level of a document and are executed when the document is being saved. If a validation error occurs, the save operation is aborted and the error is passed to the callback.
Mongoose supports different types of predefined validators, most of which are type-specific. The basic validation of any application is of course the existence of value. To validate field existence in Mongoose, you'll need to use the required
property in the field you want to validate. Let's say you want to verify the existence of a username
field before you save the user document. To do so, you'll need to make the following changes to your UserSchema
:
var UserSchema = new Schema({
...
username: {
type: String,
trim: true,
unique: true,
required: true
},
...
});
This will validate the existence of the username
field when saving the document, thus preventing the saving of any document that doesn't contain that field.
Besides the required
validator, Mongoose also includes type-based predefined validators, such as the enum
and match
validators for strings. For instance, to validate your email
field, you would need to change your UserSchema
as follows:
var UserSchema = new Schema({
...
email: {
type: String,
index: true,
match: /.+@.+..+/
},
...
});
The usage of a match
validator here will make sure the email
field value matches the given regex expression, thus preventing the saving of any document where the e-mail doesn't conform to the right pattern.
Another example is the enum
validator, which can help you define a set of strings that are available for that field value. Let's say you add a role
field. A possible validation would look like this:
var UserSchema = new Schema({
...
role: {
type: String,
enum: ['Admin', 'Owner', 'User']
},
...
});
The preceding condition will allow the insertion of only these three possible strings, and thus prevent you from saving the document.
To learn more about predefined validators, it is recommended you to visit http://mongoosejs.com/docs/validation.html for more information.
Other than predefined validators, Mongoose also enables you to define your own custom validators. Defining a custom validator is done using the validate
property. The validate
property value should be an array consisting of a validation function and an error message. Let's say you want to validate the length of your user's password. To do so, you would have to make these changes in your UserSchema
:
var UserSchema = new Schema({ ... password: { type: String, validate: [ function(password) { return password.length >= 6; }, 'Password should be longer' ] }, ... });
This validator will make sure your user's password is at least six characters long, or else it will prevent the saving of documents and pass the error message you defined to the callback.
Mongoose validation is a powerful feature that allows you to control your model and supply proper error handling, which you can use to help your users understand what went wrong. In the upcoming Lessons, you'll see how you can use Mongoose validators to handle the user's input and prevent common data inconsistencies.
Mongoose middleware are functions that can intercept the process of the init
, validate
, save
, and remove
instance methods. Middleware are executed at the instance level and have two types: pre
middleware and post
middleware.
Pre
middleware gets executed before the operation happens. For instance, a pre-save
middleware will get executed before the saving of the document. This functionality makes pre
middleware perfect for more complex validations and default values assignment.
A pre
middleware is defined using the pre()
method of the schema object, so validating your model using a pre
middleware will look like the following code snippet:
UserSchema.pre('save', function(next) { if (...) { next() } else { next(new Error('An Error Occured')); } });
A post
middleware gets executed after the operation happens. For instance, a post-save
middleware will get executed after saving the document. This functionality makes post
middleware perfect to log your application logic.
A post
middleware is defined using the post()
method of the schema object, so logging your model's save()
method using a post
middleware will look something like the following code snippet:
UserSchema.post('save', function(next) { if(this.isNew) { console.log('A new user was created.'); } else { console.log('A user updated is details.'); } });
Notice how you can use the model isNew
property to understand whether a model instance was created or updated.
Mongoose middleware are great for performing various operations, including logging, validation, and performing various data consistency manipulations. But don't worry if you feel overwhelmed right now because later in this book, you'll understand them better.
To learn more about middleware, it is recommended that you visit http://mongoosejs.com/docs/middleware.html.
Although MongoDB doesn't support joins, it does support the reference of a document to another document using a convention named DBRef. Mongoose includes support for DBRefs using the ObjectID
schema type and the use of the ref
property. Mongoose also supports the population of the parent document with the child document when querying the database.
To understand this better, let's say you create another schema for blog posts called PostSchema
. Because a user authors a blog post, PostSchema
will contain an author
field that will be populated by a User
model instance. So, a PostSchema
will have to look like the following code snippet:
var PostSchema = new Schema({ title: { type: String, required: true }, content: { type: String, required: true }, author: { type: Schema.ObjectId, ref: 'User' } }); mongoose.model('Post', PostSchema);
Notice the ref
property telling Mongoose that the author
field will use the User
model to populate the value.
Using this new schema is a simple task. To create a new blog post, you will need to retrieve or create an instance of the User
model, create an instance of the Post
model, and then assign the post author
property with the user
instance. An example of this should be as follows:
var user = new User();
user.save();
var post = new Post();
post.author = user;
post.save();
Mongoose will create a DBRef in the MongoDB post document and will later use it to retrieve the referenced document.
Since the DBRef is only an ObjectID
reference to a real document, Mongoose will have to populate the post
instance with the user
instance. To do so, you'll have to tell Mongoose to populate the post
object using the populate()
method when retrieving the document. For instance, a find()
method that populates the author
property will look like the following code snippet:
Post.find().populate('author').exec(function(err, posts) {
...
});
Mongoose will then retrieve all the documents in the posts
collection and populate their author
attribute.
DBRefs are an awesome feature of MongoDB. Mongoose's support for this feature enables you to calmly rely on object references to keep your model organized. Later in this book, we'll use DBRef to support our application logic.
To find out more about DBRefs, it is recommended that you visit http://mongoosejs.com/docs/populate.html.
44.220.184.63