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)
How to Specify a Subdocument
Let's add a comment subdocument to the Post model.
- 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
}
})
- 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"
}
}
})
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"
})
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 subdocumentThe
$inc
operator allows you to increment a propertyEvery document in Mongoose has a
deleteOne()
method.