In this chapter, we will introduce the Mongoose plugin architecture and see how we can use it to create modular re-usable code. We will look at the syntax and how to write them and include them in our code.
By the end of this chapter, you will understand how to create and use Mongoose plugins. You will also know where to go to find existing plugins that others have written, and contribute to the community by submitting your own. You will have created some plugins in the MongoosePM application.
If we look at our schemas, we can see some common elements that we are repeating. For example, each of our schemas has an identical modifiedOn
path, and our project and task schemas each have identical createdBy
and createdOn
paths.
If you're at all familiar with the DRY (Don't Repeat Yourself) principle of coding, you'll probably want to tidy these bits up and just declare them once. This is where the Mongoose plugin architecture comes in.
Let's start by creating a schema extension for adding createdOn
and createdBy
. Inside our model/db.js
file, we can add the following code, preferably above the definitions for our two schemas:
var creationInfo = function creationInfo (schema, options) { schema.add({createdOn: { type: Date, default: Date.now }}); schema.add({createdBy: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true}}); };
This exposes a function that will allow us to plug in the paths createdOn
and createdBy
, to some of our schemas. The construct might look a little strange, but is done in this way to allow us to easily take it into a separate file as a plugin. But more on that later.
The next step is to pull these paths into our existing schemas. We can update our projectSchema
and taskSchema
definitions to remove the createdOn
and createdBy
paths. Instead, after each of the schemas are defined, we can link the creationInfo
plugin to them as shown here:
projectSchema.plugin(creationInfo); taskSchema.plugin(creationInfo);
That's all we need to do. If you run your application again, everything will still work as before, including any defaults and validators set, but your code has less repetition. The flipside of this is that your schemas are no longer self-contained and may be harder to read.
In order to make the code really re-usable and portable, we need to have it in an external file, not embedded in our db.js
file. If we do this we can reference it from multiple files, and easily copy it to other projects. Create a new file called creationInfo.js
in the model folder, with the following content:
var mongoose = require( 'mongoose' ); module.exports = exports = function creationInfo (schema, options) { schema.add({createdOn: { type: Date, default: Date.now }}); schema.add({createdBy: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true}}); };
Here we have required
mongoose and just slightly modified our creationInfo
function to become a module export function. In our db.js
file, we can remove the creationInfo
function and require
our new file instead.
var creationInfo = require('./creationInfo'),
And we're good to go! We've created a re-usable schema plugin—nice and easy. You can see how by doing this you can quickly build up a re-usable library of your own schema plugins.
Plugins also have access to all of the middleware available to schemas. There are two types of middleware hooks:
pre
: This middleware is for before the method has completedpost
: This is for after the method has completedYou can use these to hook into the methods save
(which we'll look at in just a moment), init
, validate
, and remove
.
Let's take a look at creating a schema plugin for the modifiedOn
path that we have used in all of our schemas. What would be really great is if we can not only define the path in the plugin, but also set the value—this is where plugins and middleware meet perfectly.
Let's create a new file model/modifiedOn.js
and require it in model/db.js
, as shown in the following:
var modifiedOn = require('./modifiedOn'),
In the new model/modifiedOn.js
file, add the following code:
var mongoose = require( 'mongoose' ); module.exports = exports = function modifiedOn (schema, options) { schema.add({ modifiedOn: Date }); schema.pre('save', function (next) { this.modifiedOn = Date.now(); next(); }); };
This will do two things, which are:
modifiedOn
path with a SchemaType of Date
This removes the need to manually set the value each time we run an update operation on any of the schemas. So we can go through our code and remove the other instances of user.modifiedOn = Date.now()
, project.modifiedOn = Date.now()
and thisTask.modifiedOn = Date.now()
as they are all handled in one place, every time any document (or subdocument) is saved. How's that for not repeating yourself?
The final step is to remove the modifiedOn
definitions from each schema, and reference the plugin instead:
userSchema.plugin(modifiedOn); projectSchema.plugin(modifiedOn); taskSchema.plugin(modifiedOn);
3.144.9.141