Mongoose 5.6.0 was released last week. This new release has 12 new features, 2 performance improvements, and several docs improvements. The most interesting new feature is immutable properties. The idea is that marking a property as immutable means that property cannot change after the document is created.
Motivation
One of the primary goals for Mongoose is allowing you to safely write untrusted data to MongoDB without any additional validation. Given a req.body
in Express, Mongoose should allow you to set()
the modified values and save the data to the database with minimal extra work.
Mongoose timestamps set a createdAt
when the document is created, and an updatedAt
when the document is updated. Ideally, createdAt
shouldn't change once the document is created. But, currently, you can overwrite createdAt
!
const Model = mongoose.model('Test', new Schema({
name: String
}, { timestamps: true }));
const doc = await Model.create({ name: 'test1' });
console.log(doc.createdAt); // 2019-06-18T16:24:16.733Z
// Overwrite `createdAt`
doc.set({ createdAt: new Date('2019-06-01') });
await doc.save();
console.log(doc.createdAt); // 2019-06-01T00:00:00.000Z
This means you need to be careful to strip out createdAt
from any request bodies. Otherwise, a bug or a malicious user might set an incorrect createdAt
.
Introducing Immutable Properties
In Mongoose 5.6.0, you can make a property immutable. If you mark createdAt
as immutable, Mongoose will disallow changing createdAt
.
const Model = mongoose.model('Test', new Schema({
name: String,
createdAt: {
type: Date,
immutable: true // Make `createdAt` immutable
}
}, { timestamps: true }));
const doc = await Model.create({ name: 'test1' });
console.log(doc.createdAt); // 2019-06-18T16:24:16.733Z
// Since `createdAt` is immutable, Mongoose ignores the update
// to `createdAt`
doc.set({ createdAt: new Date('2019-06-01'), name: 'test' });
await doc.save();
console.log(doc.name); // 'test'
console.log(doc.createdAt); // 2019-06-18T16:55:49.197Z
Immutability only applies to documents that have already been saved to the database. In other words, you can modify immutable properties if the document's isNew
property is false
.
const Model = mongoose.model('Test', new Schema({
name: String,
createdAt: {
type: Date,
immutable: true // Make `createdAt` immutable
}
}, { timestamps: true }));
const doc = new Model({ name: 'test1' });
doc.createdAt = new Date('2019-06-01');
await doc.save();
doc.createdAt; // 2019-06-01T00:00:00.000Z
With Update
Mongoose also strips updates to immutable properties from updateOne()
, updateMany()
, and findOneAndUpdate()
. Your update will succeed if you try to overwrite an immutable property, Mongoose will just strip out the immutable property.
const schema = new Schema({
name: String
}, { timestamps: true });
// An alternative way to make `createdAt` immutable
schema.path('createdAt').immutable(true);
const Model = mongoose.model('Test', schema);
let doc = await Model.create({ name: 'test1' });
doc = await Model.findOneAndUpdate({}, {
name: 'test',
// Because `createdAt` is immutable, Mongoose will strip `createdAt`
// from the update
createdAt: new Date('2019-06-01')
}, { new: true });
console.log(doc.name); // 'test'
console.log(doc.createdAt); // 2019-06-18T17:33:03.293Z
Moving On
Immutable properties are just one of 12 new features in Mongoose 5.6.0. Mongoose 5.6.0 also lets you use arrow functions to define virtual getters and setters, set a global maxTimeMS
for your queries, and set populate's ref
option to a function. Make sure you upgrade to take advantage of all the new features!
Want to become your team's MongoDB expert? "Mastering Mongoose" distills 8 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!