Middleware (also called pre and post hooks) are functions which are passed control during execution of asynchronous functions. Middleware is specified on the schema level and is useful for writing plugins. Mongoose 4.0 has 2 types of middleware: document middleware and query middleware. Document middleware is supported for the following document functions.
Query middleware is supported for the following Model and Query functions.
Both document middleware and query middleware support pre and post hooks. How pre and post hooks work is described in more detail below.
Note: There is no query hook for remove()
, only for documents. If you set a 'remove' hook, it will be fired when you call myDoc.remove()
, not when you call MyModel.remove()
. Note: The create()
function fires save()
hooks.
There are two types of pre
hooks, serial and parallel.
Serial middleware are executed one after another, when each middleware calls next
.
var schema = new Schema(..); schema.pre('save', function(next) { // do stuff next(); });
Parallel middleware offer more fine-grained flow control.
var schema = new Schema(..); // `true` means this is a parallel middleware. You **must** specify `true` // as the second parameter if you want to use parallel middleware. schema.pre('save', true, function(next, done) { // calling next kicks off the next middleware in parallel next(); setTimeout(done, 100); });
The hooked method, in this case save
, will not be executed until done
is called by each middleware.
Middleware are useful for atomizing model logic and avoiding nested blocks of async code. Here are some other ideas:
If any middleware calls next
or done
with a parameter of type Error
, the flow is interrupted, and the error is passed to the callback.
schema.pre('save', function(next) { // You **must** do `new Error()`. `next('something went wrong')` will // **not** work var err = new Error('something went wrong'); next(err); }); // later... myDoc.save(function(err) { console.log(err.message) // something went wrong });
post middleware are executed after the hooked method and all of its pre
middleware have completed. post
middleware do not directly receive flow control, e.g. no next
or done
callbacks are passed to it. post
hooks are a way to register traditional event listeners for these methods.
schema.post('init', function(doc) { console.log('%s has been initialized from the db', doc._id); }); schema.post('validate', function(doc) { console.log('%s has been validated (but not saved yet)', doc._id); }); schema.post('save', function(doc) { console.log('%s has been saved', doc._id); }); schema.post('remove', function(doc) { console.log('%s has been removed', doc._id); });
While post middleware doesn't receive flow control, you can still make sure that asynchronous post hooks are executed in a pre-defined order. If your post hook function takes at least 2 parameters, mongoose will assume the second parameter is a next()
function that you will call to trigger the next middleware in the sequence.
// Takes 2 parameters: this is an asynchronous post hook schema.post('save', function(doc, next) { setTimeout(function() { console.log('post1'); // Kick off the second post hook next(); }, 10); }); // Will not execute until the first middleware calls `next()` schema.post('save', function(doc, next) { console.log('post2'); next(); });
The save()
function triggers validate()
hooks, because mongoose has a built-in pre('save')
hook that calls validate()
. This means that all pre('validate')
and post('validate')
hooks get called before any pre('save')
hooks.
schema.pre('validate', function() { console.log('this gets printed first'); }); schema.post('validate', function() { console.log('this gets printed second'); }); schema.pre('save', function() { console.log('this gets printed third'); }); schema.post('save', function() { console.log('this gets printed fourth'); });
Pre and post save()
hooks are not executed on update()
, findOneAndUpdate()
, etc. You can see a more detailed discussion why in this GitHub issue. Mongoose 4.0 has distinct hooks for these functions.
schema.pre('find', function() { console.log(this instanceof mongoose.Query); // true this.start = Date.now(); }); schema.post('find', function(result) { console.log(this instanceof mongoose.Query); // true // prints returned documents console.log('find() returned ' + JSON.stringify(result)); // prints number of milliseconds the query took console.log('find() took ' + (Date.now() - this.start) + ' millis'); });
Query middleware differs from document middleware in a subtle but important way: in document middleware, this
refers to the document being updated. In query middleware, mongoose doesn't necessarily have a reference to the document being updated, so this
refers to the query object rather than the document being updated.
For instance, if you wanted to add an updatedAt
timestamp to every update()
call, you would use the following pre hook.
schema.pre('update', function() { this.update({},{ $set: { updatedAt: new Date() } }); });
New in 4.5.0
Middleware execution normally stops the first time a piece of middleware calls next()
with an error. However, there is a special kind of post middleware called "error handling middleware" that executes specifically when an error occurs.
Error handling middleware is defined as middleware that takes one extra parameter: the 'error' that occurred as the first parameter to the function. Error handling middleware can then transform the error however you want.
var schema = new Schema({ name: { type: String, // Will trigger a MongoError with code 11000 when // you save a duplicate unique: true } }); // Handler **must** take 3 parameters: the error that occurred, the document // in question, and the `next()` function schema.post('save', function(error, doc, next) { if (error.name === 'MongoError' && error.code === 11000) { next(new Error('There was a duplicate key error')); } else { next(error); } }); // Will trigger the `post('save')` error handler Person.create([{ name: 'Axl Rose' }, { name: 'Axl Rose' }]);
Error handling middleware also works with query middleware. You can also define a post update()
hook that will catch MongoDB duplicate key errors.
// The same E11000 error can occur when you call `update()` // This function **must** take 3 parameters. If you use the // `passRawResult` function, this function **must** take 4 // parameters schema.post('update', function(error, res, next) { if (error.name === 'MongoError' && error.code === 11000) { next(new Error('There was a duplicate key error')); } else { next(error); } }); var people = [{ name: 'Axl Rose' }, { name: 'Slash' }]; Person.create(people, function(error) { Person.update({ name: 'Slash' }, { $set: { name: 'Axl Rose' } }, function(error) { // `error.message` will be "There was a duplicate key error" }); });
Now that we've covered middleware, let's take a look at Mongoose's approach to faking JOINs with its query population helper.
© 2010 LearnBoost
Licensed under the MIT License.
http://mongoosejs.com/docs/middleware.html