Chapter 11. Plugins – Re-using Code

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.

Reusable schema plugins

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.

Creating a schema plugin

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.

Applying the plugin to an existing schema

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.

Using an external file

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.

Using schema middleware

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 completed
  • post: This is for after the method has completed

You 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:

  • Add the modifiedOn path with a SchemaType of Date
  • Set the value to be the current date and time just before the document is saved

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);

Not just for plugins

This schema.pre middleware can also be used directly on the parent schema itself. We could quite easily have had this for example on an individual schema:

projectSchema.pre('save', function (next) {
  this.modifiedOn = Date.now();
  next();
});
..................Content has been hidden....................

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