The populate() method in Mongoose is used to replace references stored as ObjectId in your MongoDB documents with the actual documents they reference from another collection. This method is invaluable when you want to retrieve related data from different collections in a single query.
Setting Up Mongoose and Using Populate
Before you can use populate, you need to define relationships between your collections using the ref property in Mongoose schemas. The ref property allows one schema to refer to another schema.
Step 1: Set Up Mongoose Database Connection
We will first connect mongoose with our application:
const mongoose = require("mongoose");
mongoose.connect("mongodb://127.0.0.1:27017/gfg"); Step 2: Define Schemas with References
Populate is used to "look up" documents from other collections and merge them into the current document. This is especially useful for creating relationships between documents in different collections. To use the populate feature in Mongoose, you need to first define the relationship between your documents using the "ref" property in your schema. For example:
const UserSchema = new mongoose.Schema({
name: String,
age: Number,
posts: [{ type: mongoose.Types.ObjectId, ref: "Post" }],
});
const PostSchema = new mongoose.Schema({
title: String,
author: { type: mongoose.Types.ObjectId, ref: "User" },
});
const Post = mongoose.model("Post", PostSchema);
const User = mongoose.model("User", UserSchema);In this example:
- This code shows two-way referencing.
- The User model contains an array of Post objects.
- The Post model includes a single User as its author.
Using Populate to Retrieve Related Data
To populate the author field in a Post, you can use the populate() method. The author field in the Post document will be replaced with the actual User document.
Example: Populate Single Reference
Post.findOne({ title: "This is my first post" })
.populate("author")
.exec((err, post) => {
// Will have post.author populated
if (!err) console.log(post);
process.exit(0);
});Output:

In this example:
- Retrieve a single post by its title.
- Use .populate() to fill in the author field of the post.
- Use another .populate() to fill in the author field of each comment on the post.
Since we have stored ObjectIds of users, we can populate posts in the authors' document.
User.findById("63b1332c8a41f608100eeffd")
.populate("posts")
.exec((err, user) => {
// Will have post.author populated
if (!err) console.log(user);
process.exit(0);
});Output:
.jpeg)
Customize Populating References
1. Using match for Query Conditions
The match option in the populate() method to specify a query condition for the population process. The match option takes a Mongoose query object, which allows you to specify the conditions that the documents being populated must meet.
User.findById("63b1332c8a41f608100eeffd")
.populate({
path: "posts", match: {
title: /^T/i
}, select: "title"
})
.exec((err, user) => {
// Will have post.author populated
if (!err) console.log(user);
process.exit(0);
});Output:
.jpg)
In this example:
- Retrieve a single user by name.
- Use populate() to get all posts by that user.
- match filters posts whose title starts with "T".
- select includes only the title field in the results.
2. Using options for Pagination
The options field in the populate() method specifies a set of options to pass to the MongoDB driver's find() function when executing the population query. In this case, the sort option is used to sort the posts by their "title" field in ascending order, and the limit option is used to limit the number of posts to 2.
User.findById("63b1332c8a41f608100eeffd")
.populate({
path: "posts", options: {
sort: { title: 1 }, limit: 2
}
})
.exec((err, user) => {
// Will have post.author populated
if (!err) console.log(user);
process.exit(0);
});Output:
.jpg)
3. Using Select to Include/Exclude Fields
The select option allows you to specify which fields should be included or excluded from the populated documents. You can exclude the _id field from posts if not needed.
User.findById("63b1332c8a41f608100eeffd")
.populate({ path: "posts", select: "title -_id" }) // Include title, exclude _id
.exec((err, user) => {
if (!err) console.log(user);
process.exit(0);
});Adding a - will exclude the _id field from the query.
.jpeg)
Populating Multiple Levels
We can also populate multiple levels of references by chaining multiple calls to populate(). This is useful when you have nested references, like posts having comments and comments having authors.
Example: Populate Multiple Levels
const userSchema = new mongoose.Schema({
name: String,
posts: [{ type: mongoose.Types.ObjectId, ref: 'Post' }]
});
const postSchema = new mongoose.Schema({
title: String,
content: String,
author: { type: mongoose.Types.ObjectId, ref: 'User' },
comments: [{ type: mongoose.Types.ObjectId, ref: 'Comment' }]
});
const commentSchema = new mongoose.Schema({
text: String,
author: { type: mongoose.Types.ObjectId, ref: 'User' }
});In this example:
- User model has an array of Post objects.
- Post model has an author field (a single User).
- Comment model has an author field (a single User).
To populate the references between these documents, you can use the populate() method multiple times in a query chain. For example:
User.findOne({ name: 'John' }).populate({
path: 'posts',
populate: {
path: 'comments',
model: 'Comment',
populate: {
path: 'author',
model: 'User'
}
}
}).exec((err, user) => {
console.log(user);
});We use another call to populate() to fully populate the "author" field of each comment with the corresponding "User" document.
.jpg)
Virtual Populate in Mongoose
A virtual populate is used when you don’t store the reference directly in the schema but still want to populate it using virtual fields. Virtual fields are not stored in the database but can be populated as if they are.
Example: Virtual Populate
const commentSchema = new mongoose.Schema({
text: String,
postId: { type: mongoose.Schema.Types.ObjectId, ref: 'Post' }
});
commentSchema.virtual('post', {
ref: 'Post',
localField: 'postId',
foreignField: '_id',
justOne: true
});
const Comment = mongoose.model('Comment', commentSchema);
// Populate virtual field "post"
Comment.findById('someCommentId')
.populate('post')
.exec((err, comment) => {
if (!err) console.log(comment);
process.exit(0);
});