A Beginner's Guide to Subdocuments in Mongoose

A Beginner's Guide to Subdocuments in Mongoose

As I was testing the TMDB API, I encountered many one-to-many relationships. This made me curious how one-to-many relationships are achieved in a MongoDB/Mongoose database.

The image below shows an example of one-to-many relationships such as the created by , genres and languages elements.

You can model a one-to-many relationship in Mongoose in two ways;

  • using subdocuments,

  • using document references

Subdocuments, also known as embedded documents, are schemas that are nested in a parent schema while document references is a technique where a model references another model using their id.

In this tutorial, I will explain how to use subdocuments in Mongoose to model a one-to-many relationship.

Prerequisites

  • Basic knowledge of Mongoose operations

  • MongoDB database

I will be using Node and Express but you're free to use a language of your choice.

To demonstrate how a subdocument works, we will create a model for a social media post. This is the starter template of the post model.

// in Post.js file
const mongoose = require("mongoose")

const PostSchema = new mongoose.Schema({
    picture: Buffer,
    caption: String,
    likes: {
        type: Number,
        default: 0
    },
    createdAt: {
        type: Date,
        default: Date.now
    },
    user: {
        type: mongoose.Schema.Types.ObjectId,
        ref: 'User'
    }
})

module.exports = mongoose.model('Post', PostSchema)
💡
The user property is an example of a document reference.

How to Specify a Subdocument

Let's add a comment subdocument to the Post model.

  1. Creating a Comment schema
const CommentSchema = new mongoose.Schema({
    message: {
        type: String,
        required: true
    },
    user: {
        type: mongoose.Schema.Types.ObjectId,
        ref: 'User'
    },
    createdAt: {
        type: Date,
        default: Date.now
    },
    likes: {
        type: Number,
        default: 0
    }
})
  1. Adding the comment schema to the Post model
const PostSchema = new mongoose.Schema({
    // the default values above
    comments: [CommentSchema]
})

The above code defines an array that contains documents that will be validated by the comment schema.

How to Add a Subdocument

We will add documents to the comments array by updating the post using the $push operator.

The $push operator is similar to the $set operator but it adds values to an array. The format is:

Let's create a comment.

let postId = "485hrfnw8u489rj298j4r"
await Post.updateOne({
    _id: postId
}, {
    $push: {
        comments: {
            message: "This is a nice view",
            user: "587t38hfbg8hteng8etgthhuern"
        }
    }
})
💡
Please ensure you use a valid ObjectId for the user property to avoid getting a validation error.

You can also check whether the comment was successfully added by querying the post. We will cover this in the next section.

How to Read a Subdocument

Every subdocument is assigned a unique ObjectId which makes it easier for us to query it.

To get a comment, we will use the id() method.

// get the post that has the comment
let post = Post.findById(postId)
// get the comment using its id
let comment = post.comments.id(commentId)

You can also find a subdocument using dot notation. For example, to get a user's comments, you will use this query.

await Post.find({
    "comments.user": "67hg8ueugtj8ue78576896"
})
💡
Always use strings for properties that use the dot notation.

How to Update a Subdocument

You can use the dot notation to update a document or to add new fields.

Let's update a comment's message.

await User.updateOne({
       _id: postId,
       "comments._id": id
    }, {
        $set: {
           "comments.message": "Really nice view!"
        }
})

We can also increase the likes property using the $inc.

await User.updateOne({
       _id: postId,
       "comments._id": id
   }, {
       $inc: {
           "comments.likes": 1
        }
})

How to Delete a Subdocument

We will be using the id() method to find the comment then delete the comment using deleteOne().

// get the post that has the comment
let post = Post.findById(postId)
// get the comment using its id and delete it
post.comments.id(commentId).deleteOne()

Summary

  • A subdocument allows you to have an in-built one-to-many relationship in a document without creating a new model.

  • Mongoose adds _id properties for subdocuments.

  • You can use dot notation to read and update subdocuments

  • The inbuilt id() method allows you to read a subdocument

  • The $inc operator allows you to increment a property

  • Every document in Mongoose has a deleteOne() method.