To Understand populate, you have to understand the relation between collections.
Consider a library where we have
-
A Collection Of Books which is consist of
id
,name
,author
andanother informational fields
. -
A Collection Of Users which is consist of
id
,name
andother stuff
- A Collection Of Issue which is consist of user and books, just for now ignore other stuff
Lets Begin
Think of how we can store Issue Collection record
- By saving user data and book data
[
{
"user": {
"name": "John",
"_id": "65230asa434r345d34kd3wbb",
"address": "Noida",
"email": "john@example.com"
},
"book": {
"name": "Harry Potter",
"_id": "6b6s6a9d6fj87s8978s86s7s",
"author": "J.K. Rowling",
"publication": "XYZ"
}
}
]
the problem with this approach is you end up with collecting duplicate data(User is also present in User collection and same with Books). This makes your database containing duplicate fields and hard to maintain.
But what makes it hard? I will access record fields easily!
My friend, this is hard because imagine if user has updated this email field or, the books field have been edited then we have to update records two times in Issues
Collection and Books
or User
Collection.
To make database redundant, we should move to second approach
-
By Saving
User _id
andBook _id
inIssues
Record
[
{
"user": ObjectId("65230asa434r345d34kd3wbb"),
"book": ObjectId("6b6s6a9d6fj87s8978s86s7s"),
}
]
with this approach we are storing a reference of user and book in their respective collections.
So How I Get Records? This Looks To Complex!!
My friend, here the populate comes to aid(_Why They called it Populate
because finds data with their unique id and replace the ObjectId).
Advantages
- Database is not redundant.
- Updating records in easy.
Let's Implement This
Create Book and User models
const { Schema, model } = require('mongoose');
const userSchema = new Schema({
name: String,
address: String,
email: String,
});
const User = model('User', userSchema);
const { Schema, model } = require('mongoose');
const bookSchema = new Schema({
name: String,
author: String,
publication: String,
});
const Book = model('Book', bookSchema);
Lets add some Documents('same as Records') To Collection
const john = await User.create({ // User.create() is equivalent to new User().save()
name: "John",
address: "Noida",
email: "john@example.com"
})
const harryPotter = await Book.create({
name: "Harry Potter",
author: "J.K. Rollings",
publication: "XYZ"
})
Now User Issue a book from library
So how we do it?
Here comes the populate to aid
const { Schema, model, Types } = require('mongoose');
const issuesSchema = new Schema({
user: { type: Types.ObjectId, ref: "User" },
book: { type: Types.ObjectId, ref: "Book" },
});
const Issue = model('Issue', issuesSchema);
What the is type
and ref
-
type
: it is property tells to store the ObjectId of that particular document(document is here User or book) -
ref
: It is a name of collection to find that ObjectId. Here "User" and "Book" are name of collections we created.
Let's Issue A Book
Think John Comes To Library To Issue A Book Harry Potter
To issue a book we have to create a new Issue
const issuedBook = await Issue.create({ user: john, book: harryPotter });
What's going here
Actually, we are saving john._id and harryPotter._id value to issue, in database it's look like
{ // issuedBook 1
user: "65230asa434r345d34kd3wbb",
book: "6b6s6a9d6fj87s8978s86s7s",
}
{ // issuedBook 2
user: "65230asa45645r4jjjl3434h",
book: "6b6s6a9h5j3kh7j38fw4j3k2",
}
This is how we save to refrences to user and books field
There are various ways to save refrences. Check out documentation here
How To Populate Records ?
Now, imagine libarian wants to check all issued book records.
Well this is very simple to implement
const Issue = require('./models/issue');
// Import Issue model
const issuedBooks = await Issue.find({}).populate('user').populate('book').exec()
Let's understand this what does that chain of calls doing
-
Issue.find({})
: This will find all records inIssue
Collection. You can set conditions in find -
populate('user)
: parameter 'user' is same of field which we want to populate.populate('user')
will find user by thier perspecitve id and replacesuser
field with actual user data. -
populate('book)
: same as above, replace book id with actual record -
exec()
: This is very important function call. This will execute above all populate operation. If you forgot to call this. Your field will not be populated.
Note: If During Populate Some Records Are Not Found Then These Records Are Replaced By null
Now issuedBooks
value be like
{ // issuedBook 1
user: {
name: "John",
address: "Noida",
email: "john@example.com",
_id: "65230asa434r345d34kd3wbb"
},
book: {
name: "Harry Potter",
author: "J.K. Rollings",
publication: "XYZ",
_id: "6b6s6a9d6fj87s8978s86s7s"
}
}
{ // issuedBook 2
user: {
name: "Peter",
address: "Delta",
email: "peter@example.com",
_id: "65230asa45645r4jjjl3434h"
},
book: {
name: "Invisible Man",
author: "Ralph Elipson",
publication: "ABC",
_id: "6b6s6a9h5j3kh7j38fw4j3k2"
}
}
Conclusion
This seems like a useful tool in being able to define a declarative model for what your data should look like as a result of any query. There are some inherent weaknesses to a lack of true “joins”, but the Mongoose API does an elegant job of optimizing these types of queries under the hood by using poplulate.
I’ve only recently begun using this, so if you know something that I don’t and would like to contribute to the discussion for anyone reading this article, feel free to comment below with any critiques, suggestions, random quotes, or song lyrics. Thanks
Top comments (1)
Hey! I think this article is fine and helpful, but if you're going to copy and paste text I would recommend you cite the source.
Conclusion taken from this blog post
Some comments have been hidden by the post's author - find out more