# Introduction to Mongoose Subdocuments

By Atta Ur Rehman Shah (https://attacomsian.com/about). Published May 8, 2022. Topics: Node.js, Mongoose, MongoDB.
Canonical URL: https://attacomsian.com/blog/mongoose-subdocuments

> An introduction to Mongoose subdocuments and how to write queries to create, update, and remove subdocuments from a parent document.

In Mongoose, subdocuments are documents that are embedded in other [documents](https://attacomsian.com/blog/mongoose-documents). This means that you can nest schemas in other schemas.

Mongoose has two distinct concepts of subdocuments: **arrays of subdocuments** and **single nested subdocuments**:

```javascript
const childSchema = new Schema({
  firstName: String,
  lastName: String
})

const parentSchema = new Schema({
  // Array of subdocuments
  children: [childSchema],

  // Single subdocument
  child: childSchema
})
```

Just like a normal document, a subdocument can have middleware, custom validations, virtuals, and any other feature available to top-level [schemas](https://attacomsian.com/blog/mongoose-schemas):

```javascript
childSchema.virtual('fullName').get(function () {
  return this.firstName + ' ' + this.lastName
})

childSchema.pre('save', function (next) {
  if (this.firstName === 'john') {
    return next(new Error(`John is not allowed`))
  }
  next()
})
```

The main difference is that subdocuments are not saved separately. Instead, they are saved whenever their top-level parent document is saved.

## Creating subdocuments

There are two ways to create documents that contain nested documents:

1. Passing a nested object into the constructor of the parent document
2. Adding a nested object into an already created document using [array's methods](https://attacomsian.com/blog/javascript-arrays) like `push` and `unshift`

Let us assume that we have got the following order schema that contains an array of nested line item schemas:

```javascript
const orderSchema = new Schema({
  ref: String,
  lineItems: [
    new Schema({
      name: String,
      price: Number,
      qty: Number,
      total: Number
    })
  ]
})

const Order = mongoose.model('Order', orderSchema)
```

### 1. Passing a nested object to the constructor

For this method, we create a nested object that contains both order's attributes and line items:

```javascript
const orderObj = new Order({
  ref: 'QR34',
  lineItems: [
    {
      name: 'T-shirt',
      price: 6,
      qty: 2,
      total: 12
    },
    {
      name: 'Jeans',
      price: 15,
      qty: 1,
      total: 15
    }
  ]
})

const order = await orderObj.save()
console.log(order)
```

Here is what the complete parent document looks like:

```javascript
{
  _id: '6277ddade65b3236b1eb65d5',
  ref: 'QR34',
  lineItems: [
    {
      _id: '6277ddade65b3236b1eb65d6',
      name: 'T-shirt',
      price: 6,
      qty: 2,
      total: 12
    },
    {
      _id: '6277ddade65b3236b1eb65d7',
      name: 'Jeans',
      price: 15,
      qty: 1,
      total: 15
    }
  ]
}
```

### 2. Adding subdocuments to an existing document

For this method, we first create the top-level parent document:

```javascript
const orderObj = new Order({
  ref: 'QR34'
})

const order = await orderObj.save()
console.log(order)
// { _id: 6277dfd363adcf3d99012599, ref: 'QR34', lineItems: [] }
```

Then, we edit the order to add line items:

```javascript
order.lineItems.push({
  name: 'T-shirt',
  price: 6,
  qty: 2,
  total: 12
})
order.lineItems.push({
  name: 'Jeans',
  price: 15,
  qty: 1,
  total: 15
})

await order.save()
```

You can also create a subdocument by using the `create()` method of the document array:

```javascript
await order.lineItems.create({ name: 'Jeans', price: 15, qty: 1, total: 15 })
```

## Querying subdocuments

By default, each subdocument has its own `_id` attribute. Mongoose provides a special `id()` method for scanning a document array to find a document with a given `_id`:

```javascript
const doc = order.lineItems.id(`6277ddade65b3236b1eb65d6`)
console.log(doc)
// {
//     _id: 6277ddade65b3236b1eb65d6,
//     name: 'T-shirt',
//     price: 6,
//     qty: 2,
//     total: 12
// }
```

## Updating subdocuments

The simplest way to update subdocuments is:

1. Use the `findOne()` or `findById()` to retrieve the parent document
2. Retrieve the subdocument array
3. Modify the array
4. Call the `save()` method on the parent document to persist changes

```javascript
const order = await Order.findOne({ ref: 'QR34' })

// Change T-Shirts Qty
const id = `6277ddade65b3236b1eb65d6`
order.lineItems.id(id).qty = 3
order.lineItems.id(id).total = 18

const updated = await order.save()
```

## Deleting subdocuments

In Mongoose, Each subdocument has its own `remove()` method. For an array of subdocuments, this is equivalent to calling the `pull()` method on the subdocument:

```javascript
const order = await Order.findOne({ ref: 'QR34' })

order.lineItems.id(`6277ddade65b3236b1eb65d6`).remove()
// OR
order.lineItems.pull(`6277ddade65b3236b1eb65d6`)

const updated = await order.save()
```
