An Introduction to Mongoose Arrays
Mongoose's Array class extends vanilla JavaScript arrays with additional Mongoose functionality. For example, suppose you have a blog post schema with an array of tags.
const blogPostSchema = Schema({ title: String, tags: [String] }); When you create a new BlogPost document, the tags property is an instance of the vanilla JavaScript array class. But it also has some special properties.
const blogPostSchema = Schema({ title: String, tags: [String] }, { versionKey: false }); const BlogPost = mongoose.model('BlogPost', blogPostSchema); const doc = new BlogPost({ title: 'Intro to JavaScript', tags: ['programming'] }); Array.isArray(doc.tags); // true doc.tags.isMongooseArray; // true For example, Mongoose intercepts push() calls on the tags array, and is smart enough to update the document using $push when you save() the document.
mongoose.set('debug', true); doc.tags.push('web development'); // Because of 'debug' mode, will print: // Mongoose: blogposts.updateOne({ _id: ObjectId(...) }, { '$push': { tags: { '$each': [ 'web development' ] } } }, { session: null }) await doc.save(); Document Arrays
The tags example is an array of primitives. Mongoose also supports arrays of subdocuments. Here's how you can define an array of members, each with a firstName and lastName property.
const groupSchema = Schema({ name: String, members: [{ firstName: String, lastName: String }] }); doc.members is an instance of a vanilla JavaScript array, so it has all the usual functions, like slice() and filter(). But it also has some Mongoose-specific functionality baked in.
const groupSchema = Schema({ name: String, members: [{ firstName: String, lastName: String }] }); const Group = mongoose.model('Group', groupSchema); const doc = new Group({ title: 'Jedi Order', members: [{ firstName: 'Luke', lastName: 'Skywalker' }] }); Array.isArray(doc.members); // true doc.members.isMongooseArray; // true doc.members.isMongooseDocumentArray; // true For example, if you set the 0th member's firstName, Mongoose will translate that to a set on member.0.firstName when you call save().
const groupSchema = Schema({ name: String, members: [{ firstName: String, lastName: String }] }, { versionKey: false }); const Group = mongoose.model('Group', groupSchema); const doc = new Group({ title: 'Jedi Order', members: [{ firstName: 'Luke', lastName: 'Skywalker' }] }); await doc.save(); mongoose.set('debug', true); doc.members[0].firstName = 'Anakin'; // Prints: // Mongoose: groups.updateOne({ _id: ObjectId("...") }, // { '$set': { 'members.0.firstName': 'Anakin' } }, { session: null }) await doc.save(); Caveat With Setting Array Indexes
Mongoose has a known issue with setting array indexes directly. For example, if you set doc.tags[0], Mongoose change tracking won't pick up that change.
const blogPostSchema = Schema({ title: String, tags: [String] }, { versionKey: false }); const BlogPost = mongoose.model('BlogPost', blogPostSchema); const doc = new BlogPost({ title: 'Intro to JavaScript', tags: ['programming'] }); await doc.save(); // This change won't end up in the database! doc.tags[0] = 'JavaScript'; await doc.save(); const fromDb = await BlogPost.findOne({ _id: doc._id }); fromDb.tags; // ['programming'] To work around this caveat, you need to inform Mongoose's change tracking of the change, either using the markModified() method or by explicitly calling MongooseArray#set() on the array element as shown below.
// This change works. `set()` is a special method on Mongoose // arrays that triggers change tracking. doc.tags.set(0, 'JavaScript'); await doc.save(); const fromDb = await BlogPost.findOne({ _id: doc._id }); fromDb.tags; // ['JavaScript']