Query Casting

Sponsor #native_company# — #native_desc#

The first parameter to Model.find(), Query#find(), Model.findOne(), etc. is called filter. In older content this parameter is sometimes called query or conditions. For example:

  1. const query = Character.find({ name: 'Jean-Luc Picard' });
  2. query.getFilter(); // `{ name: 'Jean-Luc Picard' }`
  3. // Subsequent chained calls merge new properties into the filter
  4. query.find({ age: { $gt: 50 } });
  5. query.getFilter(); // `{ name: 'Jean-Luc Picard', age: { $gt: 50 } }`

When you execute the query using Query#exec() or Query#then(), Mongoose casts the filter to match your schema.

  1. // Note that `_id` and `age` are strings. Mongoose will cast `_id` to
  2. // a MongoDB ObjectId and `age.$gt` to a number.
  3. const query = Character.findOne({
  4. _id: '5cdc267dd56b5662b7b7cc0c',
  5. age: { $gt: '50' }
  6. });
  7. // `{ _id: '5cdc267dd56b5662b7b7cc0c', age: { $gt: '50' } }`
  8. // Query hasn't been executed yet, so Mongoose hasn't casted the filter.
  9. query.getFilter();
  10. const doc = await query.exec();
  11. doc.name; // "Jean-Luc Picard"
  12. // Mongoose casted the filter, so `_id` became an ObjectId and `age.$gt`
  13. // became a number.
  14. query.getFilter()._id instanceof mongoose.Types.ObjectId; // true
  15. typeof query.getFilter().age.$gt === 'number'; // true

If Mongoose fails to cast the filter to your schema, your query will throw a CastError.

  1. const query = Character.findOne({ age: { $lt: 'not a number' } });
  2. const err = await query.exec().then(() => null, err => err);
  3. err instanceof mongoose.CastError; // true
  4. // Cast to number failed for value "not a number" at path "age" for
  5. // model "Character"
  6. err.message;

The strictQuery Option

By default, Mongoose does not cast filter properties that aren’t in your schema.

  1. const query = Character.findOne({ notInSchema: { $lt: 'not a number' } });
  2. // No error because `notInSchema` is not defined in the schema
  3. await query.exec();

You can configure this behavior using the strictQuery option for schemas. This option is analogous to the strict option. Setting strictQuery to true removes non-schema properties from the filter:

  1. mongoose.deleteModel('Character');
  2. const schema = new mongoose.Schema({ name: String, age: Number }, {
  3. strictQuery: true
  4. });
  5. Character = mongoose.model('Character', schema);
  6. const query = Character.findOne({ notInSchema: { $lt: 'not a number' } });
  7. await query.exec();
  8. query.getFilter(); // Empty object `{}`, Mongoose removes `notInSchema`

To make Mongoose throw an error if your filter has a property that isn’t in the schema, set strictQuery to 'throw':

  1. mongoose.deleteModel('Character');
  2. const schema = new mongoose.Schema({ name: String, age: Number }, {
  3. strictQuery: 'throw'
  4. });
  5. Character = mongoose.model('Character', schema);
  6. const query = Character.findOne({ notInSchema: { $lt: 'not a number' } });
  7. const err = await query.exec().then(() => null, err => err);
  8. err.name; // 'StrictModeError'
  9. // Path "notInSchema" is not in schema and strictQuery is 'throw'.
  10. err.message;

Implicit $in

Because of schemas, Mongoose knows what types fields should be, so it can provide some neat syntactic sugar. For example, if you forget to put $in on a non-array field, Mongoose will add $in for you.

  1. // Normally wouldn't find anything because `name` is a string, but
  2. // Mongoose automatically inserts `$in`
  3. const query = Character.findOne({ name: ['Jean-Luc Picard', 'Will Riker'] });
  4. const doc = await query.exec();
  5. doc.name; // "Jean-Luc Picard"
  6. // `{ name: { $in: ['Jean-Luc Picard', 'Will Riker'] } }`
  7. query.getFilter();