Mongoose 5.4 was released on December 14, with 13 new features. The overarching theme for 5.4 is making Mongoose SchemaTypes more configurable, but that isn't the whole story. Mongoose 5.4 has several new features that will help you make your apps more robust and concise. In this article, I'll cover the new Model.events property and the new count option for virtual populate.

The count Option for Virtual Populate

Virtual populate is a more flexible alternative to conventional populate. Virtual populate allows you to specify localField and foreignField . With Mongoose 5.4.0, you can also specify a count option that tells Mongoose to populate the number of documents, instead of the documents themselves. Below is an example:

const childSchema = new Schema({ parentId: mongoose.ObjectId }); const parentSchema = new Schema({ name: String }); parentSchema.virtual( 'childCount' , { ref: 'Child' , localField: '_id' , foreignField: 'parentId' , count: true }); const Child = mongoose.model( 'Child' , childSchema); const Parent = mongoose.model( 'Parent' , parentSchema); let p = await Parent.create({ name: 'Foo' }); const c = await Child.create({ parentId: p._id }); p = await Parent.findOne().populate( 'childCount' ); console .log(p.childCount);

Under the hood, Mongoose uses countDocuments() to populate your virtual if you specify count: true . This means better performance and less network overhead if you only need the number of documents.

Note that childCount is still a virtual. That means childCount won't show up in JSON.stringify() or Express' res.json() output unless you explicitly turn on virtuals: true in your toJSON options.

console .log( JSON .stringify(p)); console .log( JSON .stringify(p.toJSON({ virtuals: true }))); mongoose.set( 'toJSON' , { virtuals: true }); console .log( JSON .stringify(p));

The Model.events Property

Tracking errors is a pain. Mongoose error handling middleware makes it easier:

const schema = new Schema({ test: Number }); schema.post( /.*/ , function ( error, res, next ) { console .log( 'Error middleware:' , error); next(); }); const Test = mongoose.model( 'Test' , schema); await Test.create({ test: 'foo' }); await Test.findOne({ test: 'foo' });

However, error handling middleware comes with a few caveats. First, Mongoose doesn't have middleware for all model functions yet. Also, Mongoose middleware can run multiple times for cases where there's a naming conflict. For example, there's middleware for both Document#remove() and Query#remove() , so the below script will execute error handling middleware twice.

const schema = new Schema({ _id: Number }); schema.post( /.*/ , { document : true , query: true }, function ( error, res, next ) { console .log( 'Error middleware:' , error.message); next(); }); const Test = mongoose.model( 'Test' , schema); const doc = new Test({ _id: void 0 }); await doc.remove();

In Mongoose 5.4.0 we added a Model.events property. This property is a Node.js EventEmitter that emits an 'error' event whenever any operation on that model errors. Unlike with error handling middleware, you won't get a double error using remove() :

const schema = new Schema({ _id: Number }); const Test = mongoose.model( 'Test' , schema); Test.events.on( 'error' , err => console .log( 'Error event:' , err.message)); const doc = new Test({ _id: void 0 }); await doc.remove();

The Model.events property is a supplement for error handling middleware rather than a replacement. Error handling middleware is useful for transforming errors and ensuring that user-facing errors are readable. But, depending on your architecture, Model.events might make it easier to ensure all errors end up in an error tracking repository like Sentry.

Moving On