Methods and Statics

Instance Methods, Statics, and Query Helpers

Three places to hang behavior, distinguished by what this is bound to: a document, the model, or a query.

A method, a static and a query helper on the book schema
bookSchema.methods.isClassic = function (before = 1970) { return this.year < before; };
bookSchema.statics.byDecade = function () {            // this = the model
  return this.aggregate([{ $group: { _id: { $subtract: ['$year', { $mod: ['$year', 10] }] },
                                     n: { $sum: 1 } } }, { $sort: { _id: 1 } }]);
};
bookSchema.query.publishedAfter = function (y) {       // this = the query, return it
  return this.where({ year: { $gt: y } });
};
const b = await Book.findOne({ title: 'Solaris' });
console.log(b.isClassic(), await Book.byDecade());
console.log((await Book.find().publishedAfter(1960).sort('-year')).map((d) => d.title));
Output
true [ { _id: 1950, n: 1 }, { _id: 1960, n: 2 } ]
[ 'The Cyberiad', 'Solaris' ]

Methods belong to a document, so they disappear under lean(). Statics are the right home for a query callers repeat — User.findByEmail(email) — because they keep the filter in one place and can be stubbed in tests. Query helpers are the least used and the most elegant: each returns this, so they chain with the builder methods in any order and compose with populate, sort and lean.

Three rules keep this from going wrong. Use function, never an arrow, or this is not the document. Never overwrite a built-in name — a method called save or populate silently replaces Mongoose 243,355 's own. And resist the Active Record pull: keep methods about the document's own data and orchestration in a service, as the Bookshelf layering of The Bookshelf API does.