Mongoose 6.0.0 was released on August 24, 2021. We've been hard at work on the 6.0 for nearly 18 months, so we're very excited to finally share all the improvements we've made. As far as high level vanity metrics go, Mongoose 6 had 457 git commits and 52 backwards breaking changes. While Mongoose 6 is a moderately larger release than Mongoose 5, most of the changes are minor improvements that shouldn't require any massive rewrites.

It's been 3.5 years since we released Mongoose 5. We try our best to ensure Mongoose is stable, and we're hoping to avoid shipping Mongoose 7 for another few years. In the meantime, we'll continue to support Mongoose 5 and backport bug fixes as necessary. However, we will not backport any new features to Mongoose 5. There will only be patch releases to Mongoose 5.x going forward.

Without further ado, here's some of the highlights from Mongoose 6. You can find the full migration guide on the Mongoose docs.

MongoDB Driver 4.0, MongoDB 5 Support

The MongoDB Node driver team released mongodb@4.0.0 on July 13. Mongoose 6.0.0 uses v4.1.1 of the MongoDB Node driver, which comes with a few notable changes. The major benefit is full support for MongoDB 5.0, including MongoDB Atlas Serverless Instances.

Another benefit is officially supported TypeScript bindings. The MongoDB Node driver team migrated to TypeScript for the 4.0 release, so there's no longer any need for @types/mongodb.

Also, the useUnifiedTopology and useNewUrlParser options are gone. These options only existed in Mongoose 5.x and MongoDB Node driver 3.x to opt in to new internal implementations. As of Mongoose 6 these options are gone because the new internal implementations are the default, and the legacy implementations are gone. If you used useUnifiedTopology: true and useNewUrlParser: true in Mongoose 5, you should have no problems removing them when upgrading to Mongoose 6.

The result of updateOne() and updateMany() operations has changed slightly. The MongoDB Node driver changed several property names for consistency and readability, most notably the n and nModified options.

let res = await TestModel.updateMany({}, { someProperty: 'someValue' });

res.matchedCount; // Number of documents that were found that match the filter. Replaces `res.n`
res.modifiedCount; // Number of documents modified. Replaces `res.nModified`
res.upsertedCount; // Number of documents upserted. Replaces `res.upserted`

Arrays as Proxies

The long-standing #1 question on the Mongoose FAQ is "why don't my changes to arrays get saved when I update an element directly?"

doc.array[3] = 'changed';

// Won't save changes to `array`.
await doc.save();

// You need to explicitly mark the array as modified 
doc.markModified('array');
await doc.save();

In Mongoose 6, the markModified() call is no longer necessary, because Mongoose arrays are now ES6 proxies. That means Mongoose can correctly track changes when you set an array element index!

const articleSchema = new mongoose.Schema({ title: String, tags: [String] });
const Article = mongoose.model('Article', articleSchema);

const article = await Article.create({ title: 'Arrays in Mongoose 6', tags: ['javascript'] });

article.tags[0] = 'mongodb';
await article.save();

const fromDb = await Article.findById(article);
console.log(fromDb.tags); // ['mongodb'] in Mongoose 6!

Another great benefit of arrays as proxies is that strict equality checks, like Node's native assert.deepStrictEqual(), will now treat Mongoose arrays as equal to vanilla JavaScript arrays. In Mongoose 5, a Mongoose array could be assert.deepEqual() to an array, but not deepStrictEqual() because they were instances of different classes, and because deepStrictEqual() would pick up on Mongoose internals.

const article = await Article.create({ title: 'Arrays in Mongoose 6', tags: ['javascript'] });

const assert = require('assert');
// This would fail in Mongoose 5, because `articles.tag` was a subclass of `Array`, not a proxy.
assert.deepStrictEqual(article.tags, ['javascript']);

We've been wary of ES6 proxy performance for many years. However, given that proxies are now more performant than ES6 promises in our basic benchmarks, and given that Vue has switched over to proxies, we think that proxies are finally ready for production use. If proxies work well for arrays in Mongoose, we will consider switching nested properties, or documents in general, over to proxies as well.

Schema-Enforced Key Order when Saving

An unfortunate gotcha for MongoDB beginners is that object key order matters in MongoDB. Long-time Mongoose and/or MongoDB Node driver users may remember the push in 2015 to use entries-style syntax for sorting, User.find().sort([['name', 1]]). This isn't the only case where order matters. For example, equality checks on nested properties:

const schema = new Schema({
  profile: {
    name: {
      first: String,
      last: String
    }
  }
});
const Test = mongoose.model('Test', schema);

const doc = new Test({
  profile: { name: { last: 'Musashi', first: 'Miyamoto' } }
});
await doc.save();

// Before Mongoose 6, this wouldn't find any results because 'first' and 'last' are in reverse order!
await Test.findOne({ 'profile.name': { first: 'Miyamoto', last: 'Musashi' } });

In Mongoose 6, when you save() a new document, the order of the keys in the document is the same as the order of the keys in the schema, as opposed to the order of the keys in the object passed to new Test(). The above example fails in Mongoose 5 because doc.save() saves the keys in profile.name in the user-specified key order. In Mongoose 6, the above example will find the correct document, because the key order matches.

Moving On

These are just a few of the improvements we've made in Mongoose 6. Check out the migration guide for a full list of all the potentially breaking changes. Please upgrade and open any issues you find on Mongoose's GitHub page!

Found a typo or error? Open up a pull request! This post is available as markdown on Github
comments powered by Disqus