Barcelona Code School

JavaScript made me do it / Est. 2015

Using .populate() with mongoDB

using populate with mongodb

Let's take a common example of eCommerce website with two collections for the products and the categories.

Each product belongs to a certain category like "shoes", "hats", "pants" – and we want to refer to a corresponding collection from each product.

We do not want to include the actual name of category into a product's record since it will complicate renaming categories – imagine we have thousands of shoes belonging to the "shoes" category and then we decide to rename it to "footwear". To do so we will need to update all the products to replace "shoes" to "footwear".

Instead if we only refer to the "shoes" record from categories collection by it's id then we only need to update one record in the categories collection and rename it from "shoes" to "footwear". All the products pointing to this category will not be needed to get updated since the id of the category will not change.

And when we fetch data for the products in the category_id field we have id of the category record from the categories collection. But what we want to get instead is the actual name of this category. This could be easily achieved with .populate() method. Let's see how.

The product schema:

const mongoose = require("mongoose");

const productSchema = new mongoose.Schema({
  name: { type: String, required: true, unique: true },
  price: { type: Number, required: true },
  color: { type: String, required: true },
  description: { type: String, required: true },
  category_id: { type: mongoose.Types.ObjectId , required: true, unique: true, ref: 'categories' },
});

module.exports = mongoose.model("products", productSchema );

As we can see in the category_id we have id of the corresponding category from another collection, namely from "categories".

The categories schema:

const mongoose = require("mongoose");

const categorySchema = new mongoose.Schema({
  category: { type: String, required: true, unique: true },
});

module.exports = mongoose.model("categories", categorySchema);

Here we only have a name of the category.

Now what you can do is to add .populate() method when you are getting products from the DB to say that instead of category_id shown as it is (the default mongoDB random sequence of letters and numbers) we want to fetch the corresponding category's name and use it instead. For example, we want to find all the products:

async findAll (req, res) {
    try {
        let product = await products.find({}).populate('category_id')
        res.send(product)
    }
    catch(e) {
        res.send({e})
    }
}

So now all the products will come with name of category included (instead of id reference).

Let's see the results in Postman. First, this is how it will be without .populate(). You can see that in the products data fetched we can only see id of the category, obviously we can't use it for rendering purposes: using populate with mongodb

Now same request but with .populate(). Now instead of id we can see the actual name of the category which we can render right away: using populate with mongodb

🤷‍♀️

With nested cross-references we can also use .populate().

For example, we have products referring to the subcategories and those subcategories are referring to the categories. Then we will use nested .populate():

async findProducts(req, res){
        try{
            const products = await Products.find({}).populate({ 
             path: 'subcategoryId',
             populate: {
               path: 'categoryId',
               model: 'Categories'
           } 
       })
Back to posts