Sub Docs
Subdocuments are documents embedded in other documents. In Mongoose, this means you can nest schemas in other schemas. Mongoose has two distinct notions of subdocuments: arrays of subdocuments and single nested subdocuments.
var childSchema = new Schema({ name: 'string' });
var parentSchema = new Schema({
// Array of subdocuments
children: [childSchema],
// Single nested subdocuments. Caveat: single nested subdocs only work
// in mongoose >= 4.2.0
child: childSchema
});
Subdocuments are similar to normal documents. Nested schemas can have middleware, custom validation logic, virtuals, and any other feature top-level schemas can use. The major difference is that subdocuments are not saved individually, they are saved whenever their top-level parent document is saved.
var Parent = mongoose.model('Parent', parentSchema);
var parent = new Parent({ children: [{ name: 'Matt' }, { name: 'Sarah' }] })
parent.children[0].name = 'Matthew';
// `parent.children[0].save()` is a no-op, it triggers middleware but
// does **not** actually save the subdocument. You need to save the parent
// doc.
parent.save(callback);
Subdocuments have save
and validate
middleware just like top-level documents. Calling save()
on the parent document triggers the save()
middleware for all its subdocuments, and the same for validate()
middleware.
childSchema.pre('save', function (next) {
if ('invalid' == this.name) {
return next(new Error('#sadpanda'));
}
next();
});
var parent = new Parent({ children: [{ name: 'invalid' }] });
parent.save(function (err) {
console.log(err.message) // #sadpanda
});
Subdocuments’ pre('save')
and pre('validate')
middleware execute before the top-level document’s pre('save')
but after the top-level document’s pre('validate')
middleware. This is because validating before save()
is actually a piece of built-in middleware.
// Below code will print out 1-4 in order
var childSchema = new mongoose.Schema({ name: 'string' });
childSchema.pre('validate', function(next) {
console.log('2');
next();
});
childSchema.pre('save', function(next) {
console.log('3');
next();
});
var parentSchema = new mongoose.Schema({
child: childSchema,
});
parentSchema.pre('validate', function(next) {
console.log('1');
next();
});
parentSchema.pre('save', function(next) {
console.log('4');
next();
});
Finding a sub-document
Each subdocument has an _id
by default. Mongoose document arrays have a special id method for searching a document array to find a document with a given _id
.
var doc = parent.children.id(_id);
Adding sub-docs to arrays
MongooseArray methods such as push, unshift, addToSet, and others cast arguments to their proper types transparently:
var Parent = mongoose.model('Parent');
var parent = new Parent;
// create a comment
parent.children.push({ name: 'Liesl' });
var subdoc = parent.children[0];
console.log(subdoc) // { _id: '501d86090d371bab2c0341c5', name: 'Liesl' }
subdoc.isNew; // true
parent.save(function (err) {
if (err) return handleError(err)
console.log('Success!');
});
Sub-docs may also be created without adding them to the array by using the create method of MongooseArrays.
var newdoc = parent.children.create({ name: 'Aaron' });
Removing subdocs
Each subdocument has it’s own remove method. For an array subdocument, this is equivalent to calling .pull()
on the subdocument. For a single nested subdocument, remove()
is equivalent to setting the subdocument to null
.
// Equivalent to `parent.children.pull(_id)`
parent.children.id(_id).remove();
// Equivalent to `parent.child = null`
parent.child.remove();
parent.save(function (err) {
if (err) return handleError(err);
console.log('the subdocs were removed');
});
Alternate declaration syntax for arrays
If you create a schema with an array of objects, mongoose will automatically convert the object to a schema for you:
var parentSchema = new Schema({
children: [{ name: 'string' }]
});
// Equivalent
var parentSchema = new Schema({
children: [new Schema({ name: 'string' })]
});
Next Up
Now that we’ve covered Sub-documents
, let’s take a look at querying.