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']