If you’re still on Mongoose 3.x, please read the .

    Mongoose now requires Node.js >= 4.0.0 and MongoDB >= 3.0.0. MongoDB 2.6 and where both EOL-ed in 2016.

    Query middleware is now compiled when you call mongoose.model() or db.model(). If you add query middleware after calling mongoose.model(), that middleware will not get called.

    mongoose.connect() and mongoose.disconnect() now return a promise if no callback specified, or null otherwise. It does not return the mongoose singleton.

    1. // Worked in mongoose 4. Does **not** work in mongoose 5, `mongoose.connect()`
    2. // now returns a promise consistently. This is to avoid the horrible things
    3. // we've done to allow mongoose to be a thenable that resolves to itself.
    4. mongoose.connect('mongodb://localhost:27017/test').model('Test', new Schema({}));
    5. // Do this instead
    6. mongoose.connect('mongodb://localhost:27017/test');
    7. mongoose.model('Test', new Schema({}));

    The useMongoClient option was removed in Mongoose 5, it is now always true. As a consequence, Mongoose 5 no longer supports several function signatures for mongoose.connect() that worked in Mongoose 4.x if the useMongoClient option was off. Below are some examples of mongoose.connect() calls that do not work in Mongoose 5.x.

    • mongoose.connect('localhost', 'mydb', 27017);
    • mongoose.connect('mongodb://host1:27017,mongodb://host2:27017');

    In Mongoose 5.x, the first parameter to mongoose.connect() and mongoose.createConnection(), if specified, must be a . The connection string and options are then passed down to the MongoDB Node.js driver’s MongoClient.connect() function. Mongoose does not modify the connection string, although mongoose.connect() and mongoose.createConnection() support a .

    Setters run in reverse order in 4.x:

    1. const schema = new Schema({ name: String });
    2. schema.path('name').
    3. set(() => console.log('This will print 2nd')).
    4. set(() => console.log('This will print first'));

    In 5.x, setters run in the order they’re declared.

    1. const schema = new Schema({ name: String });
    2. schema.path('name').
    3. set(() => console.log('This will print first')).
    4. set(() => console.log('This will print 2nd'));

    Mongoose 5.1.0 introduced an _id getter to ObjectIds that lets you get an ObjectId regardless of whether a path is populated.

    1. const blogPostSchema = new Schema({
    2. title: String,
    3. author: {
    4. type: mongoose.Schema.Types.ObjectId,
    5. ref: 'Author'
    6. }
    7. });
    8. const BlogPost = mongoose.model('BlogPost', blogPostSchema);
    9. await BlogPost.create({ title: 'test', author: author._id });
    10. const blogPost = await BlogPost.findOne();
    11. console.log(blogPost.author); // '5b207f84e8061d1d2711b421'
    12. // New in Mongoose 5.1.0: this will print '5b207f84e8061d1d2711b421' as well
    13. console.log(blogPost.author._id);
    14. await blogPost.populate('author');
    15. console.log(blogPost.author._id); '5b207f84e8061d1d2711b421'

    As a consequence, checking whether blogPost.author._id is no longer viable as a way to check whether author is populated. Use blogPost.populated('author') != null or blogPost.author instanceof mongoose.Types.ObjectId to check whether author is populated instead.

    Note that you can call mongoose.set('objectIdGetter', false) to change this behavior.

    Return Values for `remove()` and `deleteX()`

    deleteOne(), deleteMany(), and remove() now resolve to the result object rather than the full .

    1. // In 4.x, this is how you got the number of documents deleted
    2. MyModel.deleteMany().then(res => console.log(res.result.n));
    3. // In 5.x this is how you get the number of documents deleted
    4. MyModel.deleteMany().then(res => res.n);

    The useMongooseAggCursor option from 4.x is now always on. This is the new syntax for aggregation cursors in mongoose 5:

    1. // cursor.
    2. const cursor = MyModel.aggregate([{ $match: { name: 'Val' } }]).cursor().exec();
    3. // No need to `await` on the cursor or wait for a promise to resolve
    4. cursor.eachAsync(doc => console.log(doc));
    5. // Can also pass options to `cursor()`
    6. const cursorWithOptions = MyModel.
    7. aggregate([{ $match: { name: 'Val' } }]).
    8. cursor({ batchSize: 10 }).
    9. exec();

    Model.geoNear() has been removed because the MongoDB driver no longer supports it

    Required URI encoding of connection strings

    Due to changes in the MongoDB driver, connection strings must be URI encoded.

    If they are not, connections may fail with an illegal character message.

    If your app is used by a lot of different connection strings, it’s possible that your test cases will pass, but production passwords will fail. Encode all your connection strings to be safe.

    If you want to continue to use unencoded connection strings, the easiest fix is to use the mongodb-uri module to parse the connection strings, and then produce the properly encoded versions. You can use a function like this:

    The function above is safe to use whether the existing string is already encoded or not.

    Domain sockets

    Domain sockets must be URI encoded. For example:

    1. // Works in mongoose 4. Does **not** work in mongoose 5 because of more
    2. // stringent URI parsing.
    3. const host = '/tmp/mongodb-27017.sock';
    4. mongoose.createConnection(`mongodb://aaron:psw@${host}/fake`);
    5. // Do this instead
    6. const host = encodeURIComponent('/tmp/mongodb-27017.sock');
    7. mongoose.createConnection(`mongodb://aaron:psw@${host}/fake`);

    `toObject()` Options

    The options parameter to toObject() and toJSON() merge defaults rather than overwriting them.

    1. // Note the `toObject` option below
    2. const schema = new Schema({ name: String }, { toObject: { virtuals: true } });
    3. schema.virtual('answer').get(() => 42);
    4. const MyModel = db.model('MyModel', schema);
    5. const doc = new MyModel({ name: 'test' });
    6. // In mongoose 4.x this prints "undefined", because `{ minimize: false }`
    7. // overwrites the entire schema-defined options object.
    8. // In mongoose 5.x this prints "42", because `{ minimize: false }` gets
    9. // merged with the schema-defined options.
    10. console.log(doc.toJSON({ minimize: false }).answer);

    Aggregate Parameters

    aggregate() no longer accepts a spread, you must pass your aggregation pipeline as an array. The below code worked in 4.x:

    1. MyModel.aggregate({ $match: { isDeleted: false } }, { $skip: 10 }).exec(cb);

    The above code does not work in 5.x, you must wrap the $match and $skip stages in an array.

    1. MyModel.aggregate([{ $match: { isDeleted: false } }, { $skip: 10 }]).exec(cb);

    Boolean Casting

    By default, mongoose 4 would coerce any value to a boolean without error.

    1. // Fine in mongoose 4, would save a doc with `boolField = true`
    2. const MyModel = mongoose.model('Test', new Schema({
    3. boolField: Boolean
    4. }));
    5. MyModel.create({ boolField: 'not a boolean' });

    Mongoose 5 only casts the following values to true:

    • true
    • 'true'
    • '1'
    • 'yes'

    And the following values to false:

    • false
    • 'false'
    • 0
    • '0'
    • 'no'

    All other values will cause a CastError

    Query Casting

    Casting for update(), updateOne(), updateMany(), replaceOne(), remove(), deleteOne(), and deleteMany() doesn’t happen until exec(). This makes it easier for hooks and custom query helpers to modify data, because mongoose won’t restructure the data you passed in until after your hooks and query helpers have ran. It also makes it possible to set the overwrite option after passing in an update.

    1. // In mongoose 4.x, this becomes `{ $set: { name: 'Baz' } }` despite the `overwrite`
    2. // In mongoose 5.x, this overwrite is respected and the first document with
    3. // `name = 'Bar'` will be replaced with `{ name: 'Baz' }`
    4. User.where({ name: 'Bar' }).update({ name: 'Baz' }).setOptions({ overwrite: true });

    Post Save Hooks Get Flow Control

    Post hooks now get flow control, which means async post save hooks and child document post save hooks execute before your save() callback.

    The `$pushAll` Operator

    $pushAll is no longer supported and no longer used internally for save(), since it has been . Use $push with $each instead.

    The retainKeyOrder option was removed, mongoose will now always retain the same key position when cloning objects. If you have queries or indexes that rely on reverse key order, you will have to change them.

    Setters now run on queries by default, and the old runSettersOnQuery option has been removed.

    1. const schema = new Schema({
    2. email: { type: String, lowercase: true }
    3. const Model = mongoose.model('Test', schema);
    4. Model.find({ email: 'FOO@BAR.BAZ' }); // Converted to `find({ email: 'foo@bar.baz' })`

    The saveErrorIfNotFound option was removed, mongoose will now always error out from save() if the underlying document was not found

    init hooks are now fully synchronous and do not receive next() as a parameter.

    Document.prototype.init() no longer takes a callback as a parameter. It was always synchronous, just had a callback for legacy reasons.

    doc.save() no longer passes numAffected as a 3rd param to its callback.

    doc.remove() no longer debounces

    getPromiseConstructor() is gone, just use mongoose.Promise.

    You cannot pass parameters to the next pre middleware in the chain using next() in mongoose 5.x. In mongoose 4, next('Test') in pre middleware would call the next middleware with ‘Test’ as a parameter. Mongoose 5.x has removed support for this.

    In mongoose 5 the required validator only verifies if the value is an array. That is, it will not fail for empty arrays as it would in mongoose 4.

    In mongoose 5 the default debug function uses console.info() to display messages instead of console.error().

    In Mongoose 4.x, overwriting a filter property that’s a primitive with one that is an object would silently fail. For example, the below code would ignore the where() and be equivalent to Sport.find({ name: 'baseball' })

    1. Sport.find({ name: 'baseball' }).where({name: {$ne: 'softball'}});

    In Mongoose 5.x, the above code will correctly overwrite 'baseball' with { $ne: 'softball' }

    Mongoose 5.x uses version 3.x of the MongoDB Node.js driver. MongoDB driver 3.x changed the format of the result of so there is no longer a top-level nInserted, nModified, etc. property. The new result object structure is described here.

    1. const Model = mongoose.model('Test', new Schema({ name: String }));
    2. const res = await Model.bulkWrite([{ insertOne: { document: { name: 'test' } } }]);
    3. console.log(res);

    In Mongoose 4.x, the above will print:

    1. BulkWriteResult {
    2. ok: [Getter],
    3. nInserted: [Getter],
    4. nUpserted: [Getter],
    5. nMatched: [Getter],
    6. nModified: [Getter],
    7. nRemoved: [Getter],
    8. getInsertedIds: [Function],
    9. getUpsertedIds: [Function],
    10. getUpsertedIdAt: [Function],
    11. getRawResponse: [Function],
    12. hasWriteErrors: [Function],
    13. getWriteErrorCount: [Function],
    14. getWriteErrorAt: [Function],
    15. getWriteErrors: [Function],
    16. getLastOp: [Function],
    17. getWriteConcernError: [Function],
    18. toJSON: [Function],
    19. toString: [Function],
    20. isOk: [Function],
    21. insertedCount: 1,
    22. matchedCount: 0,
    23. modifiedCount: 0,
    24. deletedCount: 0,
    25. upsertedCount: 0,
    26. upsertedIds: {},
    27. insertedIds: { '0': 5be9a3101638a066702a0d38 },
    28. n: 1 }

    In Mongoose 5.x, the script will print:

    1. BulkWriteResult {
    2. result:
    3. { ok: 1,
    4. writeErrors: [],
    5. writeConcernErrors: [],
    6. insertedIds: [ [Object] ],
    7. nInserted: 1,
    8. nUpserted: 0,
    9. nMatched: 0,
    10. nModified: 0,
    11. nRemoved: 0,
    12. upserted: [],
    13. lastOp: { ts: [Object], t: 1 } },
    14. insertedCount: 1,
    15. matchedCount: 0,
    16. modifiedCount: 0,
    17. deletedCount: 0,
    18. upsertedCount: 0,
    19. upsertedIds: {},
    20. insertedIds: { '0': 5be9a1c87decfc6443dd9f18 },
    21. n: 1 }

    Strict SSL Validation

    The most recent versions of the , which may lead to errors if you’re using self-signed certificates.

    If this is blocking you from upgrading, you can set the tlsInsecure option to .

    1. mongoose.connect(uri, { tlsInsecure: false }); // Opt out of additional SSL validation