Mongoose 4.5 introduced custom query methods, which enabled you to extend Mongoose's chainable query builder with your own custom helper functions. By attaching a function to your schemas query property, you could create neat helper functions to encapsulate common query logic.

var ProjectSchema = new Schema({ name: String , stars: Number }); ProjectSchema.query.byName = function ( name ) { return this .find({ name: name }); }; var Project = mongoose.model( 'Project' , ProjectSchema); Project.find().where( 'stars' ).gt( 1000 ).byName( 'mongoose' );

Mongoose queries automatically cast, so you don't have to worry about a non-string name in your query:

Project.find().byName({ notA: String }).exec( function ( error ) { console .log(error); });

That works for casting errors, but what about if your query method wants to report an error? Enter in the Query.prototype.error() function.

Using this.error() to Report Errors

The above byName() function has a few potentially unexpected behaviors. For example, it is perfectly OK to pass a regular expression to the byName() function.

const mongoose = require ( 'mongoose' ); mongoose.Promise = global.Promise; mongoose.connect( 'mongodb://localhost:27017/test' , { useMongoClient: true }); mongoose.set( 'debug' , true ); var ProjectSchema = new mongoose.Schema({ name: String , stars: Number }); ProjectSchema.query.byName = function ( name ) { return this .find({ name: name }); }; var Project = mongoose.model( 'Project' , ProjectSchema); run().catch(error => console .error(error.stack)); async function run ( ) { await mongoose.connection.dropDatabase(); await Project.create({ name: 'mongoose' , stars: 13000 }); const projects = await Project.find().byName( /.*e.*/i ); console .log(projects); }

Regular expressions in MongoDB are useful, but have some performance ramifications. In order to avoid a potential index miss on a huge collection, you might want to disallow passing regexps to the byName() function. To do this, you should call this.error() in your byName() function. The query promise will then reject before actually executing the query.

var ProjectSchema = new mongoose.Schema({ name: String , stars: Number }); ProjectSchema.query.byName = function ( name ) { if (name instanceof RegExp ) { return this .error( new Error ( `Parameter to byName() cannot be a regexp, got ${name} ` )); } return this .find({ name: name }); }; var Project = mongoose.model( 'Project' , ProjectSchema); run().catch(error => console .error(error.stack)); async function run ( ) { await mongoose.connection.dropDatabase(); await Project.create({ name: 'mongoose' , stars: 13000 }); const projects = await Project.find().byName( /.*e.*/i ); console .log(projects); }

The error will not be reported until you actually try to execute the query. So you will get a promise rejection rather than an exception, and not until you call .exec() or .then() . For example, you can clear the error with .error(null) .

async function run ( ) { await mongoose.connection.dropDatabase(); await Project.create({ name: 'mongoose' , stars: 13000 }); const projects = await Project.find().byName( /.*e.*/i ). error( null ); console .log(projects); }

.error() records the last error that occurred, so errors that occur later will overwrite previous errors. Casting happens after all custom query logic, so cast errors will overwrite any user-reported errors.

async function run ( ) { await mongoose.connection.dropDatabase(); await Project.create({ name: 'mongoose' , stars: 13000 }); try { await Project.find({ stars: 'bad value' }).byName( /.*e.*/i ); } catch (error) { console .log(error); } try { await Project.find().byName( /a/ ).byName( /b/ ); } catch (error) { console .log(error); } }

Why Not Just throw ?

Another alternative is to throw an error in your custom query method.

var ProjectSchema = new mongoose.Schema({ name: String , stars: Number }); ProjectSchema.query.byName = function ( name ) { if (name instanceof RegExp ) { throw new Error ( `Parameter to byName() cannot be a regexp, got ${name} ` ); } return this .find({ name: name }); }; var Project = mongoose.model( 'Project' , ProjectSchema);

If you're using async/await, the above code works as well the .error() logic. The try/catch blocks will catch the synchronous error, but the error thrown will be the first error, rather than the last.

async function run ( ) { await mongoose.connection.dropDatabase(); await Project.create({ name: 'mongoose' , stars: 13000 }); try { await Project.find({ stars: 'bad value' }).byName( /.*e.*/i ); } catch (error) { console .log(error); } try { await Project.find().byName( /a/ ).byName( /b/ ); } catch (error) { console .log(error); } }

Throwing an exception in the custom query method is a viable alternative if you're using async/await, but not if you're using promise chaining. Currently, exceptions in custom query methods bubble up as a synchronous exception. Also, if your custom query method throws an exception then no other functions in the chain will execute. In the Project.find().byName(/a/).byName(/b/) , the byName(/b/) call never executes. Whether this is correct for your application is open to debate.

Moving On

Mongoose 4.12 has 8 new features, including improved connection events and single embedded discriminators. Make sure you upgrade to take advantage of these new features, including custom query method errors!