DEV Community

Cover image for You Don't Know About Populate
Harsh Rastogi
Harsh Rastogi

Posted on

You Don't Know About Populate

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 and another informational fields.
  • A Collection Of Users which is consist of id, name and other stuff
  • A Collection Of Issue which is consist of user and books, just for now ignore other stuff

Database Collection Showing User And Books


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"
      }
    }
  ]
Enter fullscreen mode Exit fullscreen mode

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 and Book _id in Issues Record
  [
    {
      "user": ObjectId("65230asa434r345d34kd3wbb"),
      "book": ObjectId("6b6s6a9d6fj87s8978s86s7s"),
    }
  ]
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode
  const { Schema, model } = require('mongoose');

  const bookSchema = new Schema({
    name: String,
    author: String,
    publication: String,
  });

  const Book = model('Book', bookSchema);
Enter fullscreen mode Exit fullscreen mode

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"
  })
Enter fullscreen mode Exit fullscreen mode
  const harryPotter = await Book.create({
    name: "Harry Potter",
    author: "J.K. Rollings",
    publication: "XYZ"
  })
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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 });
Enter fullscreen mode Exit fullscreen mode

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",
  }
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
  const issuedBooks = await Issue.find({}).populate('user').populate('book').exec()
Enter fullscreen mode Exit fullscreen mode

Let's understand this what does that chain of calls doing

  1. Issue.find({}): This will find all records in Issue Collection. You can set conditions in find
  2. populate('user): parameter 'user' is same of field which we want to populate. populate('user') will find user by thier perspecitve id and replaces user field with actual user data.
  3. populate('book): same as above, replace book id with actual record
  4. 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"
     }
  }
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
dylanoshima profile image
Info Comment hidden by post author - thread only accessible via permalink
Dylan

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