Mongoose 7.1.0 was released on April 27, 2023 and includes a couple of interesting new features. The most interesting new feature is BigInt support, which In this blog post, I'll also cover the new createCollections() function.

Working with BigInts in Mongoose

Mongoose 7.1 introduces a new BigInt SchemaType, so you can define paths as BigInts in your schema.

const schema = new Schema({
  answer: BigInt
});
const Question = mongoose.model('Question', schema);

const doc = new Question({ answer: 42n });
doc.answer; // 42n
typeof doc.answer; // 'bigint'

Mongoose handles basic casting for BigInts, including converting strings and numbers to BigInts.

const doc1 = new Question({ answer: 42 });
doc1.answer; // 42n

const doc2 = new Question({ answer: '42' });
doc2.answer; // 42n

const doc3 = new Question({ answer: 'foobar' });
await doc3.validate(); // Throws a CastError

BigInts in JavaScript theoretically support arbitrary large integers. But, with Mongoose, there is a limitation: the MongoDB Node.js driver stores BigInts as 64-bit integers in MongoDB. So, while you can represent a number larger than 2^63 - 1 as a BigInt in JavaScript, MongoDB will store that number as 0.

// Stores answer as `answer: new Long("9223372036854775807")`
await Question.create({ answer: 2n**63n - 1n });

// Stores answer as 0
await Question.create({ answer: 2n**64n });

To work around this, we recommend adding a custom validator to BigInts:

const schema = new Schema({
  answer: {
    type: BigInt,
    validate: v => v == null || (v < 2n**63n && v > (-2n)**63n)
  }
});

// With the above schema, the following code throws this error:
// "Question validation failed: answer: Validator failed for path `answer`
// with value `18446744073709551616`"
await Question.create({ answer: 2n**64n });

While BigInts are practically limited in size with Mongoose, they do still offer support for a wider range of integers than plain old JavaScript numbers. Remember that, with JavaScript numbers, the max safe integer is 2**53 - 1. With Mongoose, you can store integers up to 2**63 - 1 in MongoDB, and work with even larger integers in memory.

The createCollections() Function

Mongoose connections now have a createCollections() function that executes createCollection() for every model currently registered on the connection.

const BlogPost = mongoose.model('BlogPost', blogPostSchema);
const User = mongoose.model('User', userSchema);

// Calls `BlogPost.createCollection()`, `User.createCollection()`, etc.
await mongoose.connection.createCollections();

Most of the time, you don't need to explicitly create collections in MongoDB. If you try to insert a document into a non-existent collection, MongoDB will first create the collection for you. However, there are a number of newer MongoDB features that require you to be more careful about creating collections:

  1. Collations. You can't modify a collection's default collation after you've created the collection.
  2. Timeseries collections and capped collections. You can't change a collection's type once it has been created.

Mongoose lets you configure some collection options, like collation, timeseries, etc. in your schema. For example, the following is how you can define a capped collection in a Mongoose schema.

const schema = new Schema({ name: String }, { capped: { size: 1024 } });
const Test = mongoose.model('Test', schema);

Unless you set the autoCreate option, Mongoose won't automatically create the capped collection for you. The createCollections() function gives you a neat alternative to autoCreate that lets you create all your collections, using the options defined in your schemas, with a single command. This is especially convenient for testing purposes, where you want to wait for Mongoose to finish creating collections before using them.

Keep in mind that createCollections() currently will not throw an error if the collection already exists, even if the collection has different options.

// Create a non-capped collection
let schema = new Schema({ name: String });
let Test = mongoose.model('Test', schema);
await Test.createCollection();

schema = new Schema({ name: String }, { capped: { size: 1024 } });
mongoose.deleteModel('Test');
let Test = mongoose.model('Test', schema);
// Does **not** throw an error, even though it couldn't create a capped collection
// because a non-capped collection with the same name already exists.
await Test.createCollection();

For tests, I recommend calling dropDatabase() before calling createCollections() to ensure that your collections are correctly configured. We plan on adding support for diffing collection options in the future, so Mongoose can tell you if the underlying collection is equivalent to your schema options or not. Mongoose may even be able to use collMod to automatically update some schema options.

Moving On

Mongoose 7.1 is a relatively small release, with just 5 new features. BigInt support and createCollections() are the most noteworthy, but Mongoose 7.1 also expands UUID support and adds a new isPathSelectedInclusive() helper to queries for plugins. Make sure you upgrade to take advantage of the new features!

Want to become your team's MongoDB expert? "Mastering Mongoose" distills 10 years of hard-earned lessons building Mongoose apps at scale into 153 pages. That means you can learn what you need to know to build production-ready full-stack apps with Node.js and MongoDB in a few days. Get your copy!

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