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.

  1. var childSchema = new Schema({ name: 'string' });
  2. var parentSchema = new Schema({
  3. // Array of subdocuments
  4. children: [childSchema],
  5. // Single nested subdocuments. Caveat: single nested subdocs only work
  6. // in mongoose >= 4.2.0
  7. child: childSchema
  8. });

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.

  1. var Parent = mongoose.model('Parent', parentSchema);
  2. var parent = new Parent({ children: [{ name: 'Matt' }, { name: 'Sarah' }] })
  3. parent.children[0].name = 'Matthew';
  4. // `parent.children[0].save()` is a no-op, it triggers middleware but
  5. // does **not** actually save the subdocument. You need to save the parent
  6. // doc.
  7. 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.

  1. childSchema.pre('save', function (next) {
  2. if ('invalid' == this.name) {
  3. return next(new Error('#sadpanda'));
  4. }
  5. next();
  6. });
  7. var parent = new Parent({ children: [{ name: 'invalid' }] });
  8. parent.save(function (err) {
  9. console.log(err.message) // #sadpanda
  10. });

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.

  1. // Below code will print out 1-4 in order
  2. var childSchema = new mongoose.Schema({ name: 'string' });
  3. childSchema.pre('validate', function(next) {
  4. console.log('2');
  5. next();
  6. });
  7. childSchema.pre('save', function(next) {
  8. console.log('3');
  9. next();
  10. });
  11. var parentSchema = new mongoose.Schema({
  12. child: childSchema,
  13. });
  14. parentSchema.pre('validate', function(next) {
  15. console.log('1');
  16. next();
  17. });
  18. parentSchema.pre('save', function(next) {
  19. console.log('4');
  20. next();
  21. });

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.

  1. 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:

  1. var Parent = mongoose.model('Parent');
  2. var parent = new Parent;
  3. // create a comment
  4. parent.children.push({ name: 'Liesl' });
  5. var subdoc = parent.children[0];
  6. console.log(subdoc) // { _id: '501d86090d371bab2c0341c5', name: 'Liesl' }
  7. subdoc.isNew; // true
  8. parent.save(function (err) {
  9. if (err) return handleError(err)
  10. console.log('Success!');
  11. });

Sub-docs may also be created without adding them to the array by using the create method of MongooseArrays.

  1. 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.

  1. // Equivalent to `parent.children.pull(_id)`
  2. parent.children.id(_id).remove();
  3. // Equivalent to `parent.child = null`
  4. parent.child.remove();
  5. parent.save(function (err) {
  6. if (err) return handleError(err);
  7. console.log('the subdocs were removed');
  8. });

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:

  1. var parentSchema = new Schema({
  2. children: [{ name: 'string' }]
  3. });
  4. // Equivalent
  5. var parentSchema = new Schema({
  6. children: [new Schema({ name: 'string' })]
  7. });

Next Up

Now that we’ve covered Sub-documents, let’s take a look at querying.