Mongoose 5.4 was released on December 14, with 13 new features. The overarching theme for the most important new features is making Mongoose SchemaTypes configurable at the level of individual types. Before digging in to the new features, let's first review what a SchemaType is.

What Are SchemaTypes?

In Mongoose, a schema defines the shape of a document: what properties the document should have, and what types those properties should be. In Mongoose, A schema contains zero or more SchemaType instances, and each SchemaType instance defines what type a single property should be. For example:

const mongoose = require ( 'mongoose' ); const schema = new mongoose.Schema({ name: String }, { _id: false }); schema.path( 'name' ) instanceof mongoose.SchemaType; schema.path( 'name' ) instanceof mongoose.Schema.Types.String; console .log(schema.path( 'name' )); schema.eachPath((path, schematype) => console .log(schematype.path));

SchemaType is a class, and there are several classes that inherit from SchemaType :

mongoose.Schema.Types.String

mongoose.Schema.Types.Number or, equivalently, mongoose.Number

or, equivalently, mongoose.Schema.Types.Date

mongoose.Schema.Types.Buffer

mongoose.Schema.Types.Mixed , or, equivalently, mongoose.Mixed

, or, equivalently, mongoose.Schema.Types.ObjectId , or, equivalently, mongoose.ObjectId

, or, equivalently, mongoose.Schema.Types.Decimal128 , or, equivalently, mongoose.Decimal128

When you define a path count in your schema with type: Number , Mongoose creates an instance of the mongoose.Number , and schema.path('count').path === 'count' .

Configuring SchemaType Classes

Before Mongoose 5.4, the SchemaType class had no static properties. You could add validation, getters, setters, and other custom behavior to individual SchemaType instances, but not to all SchemaType instances.

Mongoose 5.4 added several static functions to the SchemaType class: cast() , get() , and checkRequired() . These functions let you configure behavior for all instances of a SchemaType.

For example, several people have requested that Mongoose cast empty string '' to false for boolean types. With Mongoose 5.4, you can use mongoose.Boolean.cast() to wrap the logic Mongoose uses to cast booleans:

const mongoose = require ( 'mongoose' ); const original = mongoose.Schema.Types.Boolean.cast(); mongoose.Schema.Types.Boolean.cast(v => { if (v === '' ) { return false ; } return original(v); }); const schema = new mongoose.Schema({ test: Boolean }, { _id: false }); const Model = mongoose.model( 'Test' , schema); const doc = new Model({ test: '' }); doc.test; doc.validateSync();

You can also pass false to cast() to disable casting for a given SchemaType:

const mongoose = require ( 'mongoose' ); mongoose.Number.cast( false ); const schema = new mongoose.Schema({ test: Number }, { _id: false }); const Model = mongoose.model( 'Test' , schema); const doc = new Model({ test: '123' }); doc.validateSync();

SchemaType Getters

The SchemaType.get() function lets you define a custom getter for all instances of a given SchemaType. For example, many people have requested Mongoose automatically convert MongoDB ObjectIds to hex strings. Because a MongoDB ObjectId is an object, comparing ObjectIds using built-in JavaScript functions is cumbersome:

$ node -v v8.9.4 $ node > const mongoose = require('mongoose') undefined > const oid1 = new mongoose.Types.ObjectId() undefined > const oid2 = new mongoose.Types.ObjectId(oid1.toString()) undefined > oid1 5c2e35400844102978e69f8c > oid2 5c2e35400844102978e69f8c > oid1 == oid2 false > oid1.toString() === oid2.toString() true > [oid1].indexOf(oid2) -1 > [oid1].includes(oid2) false

Before Mongoose 5.4, you could define a custom getter on each individual ObjectId path that would convert ObjectIds to strings for you:

const assert = require ( 'assert' ); const mongoose = require ( 'mongoose' ); const schema = new mongoose.Schema({ test: { type: mongoose.ObjectId, get: v => v.toString() } }, { _id: false }); const Model = mongoose.model( 'Test' , schema); const doc = new Model({ test: '5c2e35400844102978e69f8c' }); assert.ok( typeof doc.test === 'string' ); assert.ok(doc.test === '5c2e35400844102978e69f8c' );

However, you would have to add a getter to every ObjectId path. With SchemaType.get() , you can add the above getter to every ObjectId path using the below one-liner.

mongoose.ObjectId.get(v => v.toString());

Here's an example of comparing ObjectIds using === with a global getter.

const assert = require ( 'assert' ); const mongoose = require ( 'mongoose' ); mongoose.ObjectId.get(v => v.toString()); const schema = new mongoose.Schema({ test: mongoose.ObjectId }, { _id: false }); const Model = mongoose.model( 'Test' , schema); const doc = new Model({ test: '5c2e35400844102978e69f8c' }); assert.ok( typeof doc.test === 'string' ); assert.ok(doc.test === '5c2e35400844102978e69f8c' ); const doc2 = new Model({ test: doc.test.toString() }); assert.ok(doc.test === doc2.test); const doc3 = new Model({ test: 'bad' }); assert.ok(doc3.validateSync() instanceof Error );

Moving On