Search code examples
node.jsarrayspopulatemongoose-populate

Nested populate array of ObjectId


I'm trying to populate array of ObjectId in mongoose. And inside that array, will need populate again.

Let's say I've User data schema as below:-

  • models/User.js

    /** Dependencies */
    // Mongoose
    const mongoose = require('mongoose')
    
    /** Data Schema */
    // User Data Schema
    const UserSchema = new mongoose.Schema({
        // User's Name
        name: {
            // User's Firstname
            firstName: { type: String, required: true, min: 4 },
            // User's Lastname
            lastName: { type: String, required: true, min: 4 }
        },
        // User's Address Array[ID]
        address: [{
            // AddressID
            addressID: { type: mongoose.Schema.Types.ObjectId, ref: "Address" },
        }]
    })
    
    /** Data Schema Exports */
    module.exports = mongoose.model('User', UserSchema);
    
  • and Address schema, models/Address.js

    /** Dependencies */
    // Mongoose
    const mongoose = require('mongoose')
    
    /** Data Schema */
    // Address Data Schema
    const AddressSchema = new mongoose.Schema({
        // Address 1
        addressOne: { type: String, required: true },
        // Address 2
        addressTwo: { type: String },
        // Postcode
        postcode: { type: String, required: true },
        // City
        city: { type: String, required: true },
        // StateID
        state: { type: mongoose.Schema.Types.ObjectId, ref: "State" },
        // CountryID
        country: { type: mongoose.Schema.Types.ObjectId, ref: "Country" }
    })
    
    // State Data Schema
    const StateSchema = new mongoose.Schema({
        // Gender's Name
        name: { type: String, required: true }
    })
    
    // Country Data Schema
    const CountrySchema = new mongoose.Schema({
        // Race's Name
        name: { type: String, required: true }
    })
    
    /** Data Schema Export */
    const Address = mongoose.model('Address', AddressSchema)
    const State = mongoose.model('State', StateSchema)
    const Country = mongoose.model('Country', CountrySchema)
    module.exports = {
        Address,
        State,
        Country
    }
    

I know how to populate state & country from Address, like show below:-

Address.find()
.populate('state')
.populate('country')
.then(async address => {
    // do stuff
})
.catch(err => res.json({ err }))

But how can I populate array of ObjectId. I did the code like shown below:-

User.findById({ _id: userId }) // let's say I've the userId
.populate('address')
.then(async user => {
    console.log(await user)
})
.catch(err => res.json({ err }))

Unfortunately, It returns me something like this:-

{
    "_id": "5fabababababababababab1"
    "name": {
        "firstName": "Lalapolalaa",
        "lastName": "Newb"
    },
    "address": [
        {
            "_id": "5fcdcdcdcdcdcdcdcdc1",
            "addressID": "5fefefefefefefefefef1" // should've populate (but not working)
        }
    ],
    "__v": 0   
}

What I'm trying to get is like shown below:-

{
    "_id": "5fabababababababababab1"
    "name": {
        "firstName": "Lalapolalaa",
        "lastName": "Newb"
    },
    "address": [
        {
            "_id": "5fcdcdcdcdcdcdcdcdc1",
            "addressID": { // populate happens here
                "_id": "5fefefefefefefefefef1",
                "addressOne": "Lot 1, Street 12",
                "addressTwo": "SS 21",
                "postcode" : "47500",
                "city": "Subang Jaya",
                "state": { // populate happens here
                    "_id": "5fghghghghghghghghg1",
                    "name": "Selangor",
                    "__v": 0
                },
                "country": { // populate happens here
                    "_id": "5ijijijijijijijijij1",
                    "name": "Malaysia",
                    "__v": 0
                }
                "__v": 0
            }
        }
    ],
    "__v": 0   
}

How can I get that (shown above) with my current code below:-

User.findById({ _id: userId }) // let's say I've the userId
.populate('address') // assuming this populate of address works
.then(async user => {
    // how to populate state & country of Address?
    // code below only return address[0] information (assumption)
    const address = await Address.findById({ _id: user.address[0].addressID._id })
    .populate('state')
    .populate('country')
    .then(address => address)
})
.catch(err => res.json({ err }))

Solution

  • I'm able to find the solution myself with below corrections:-

    • in models/User.js:-
    /** Dependencies */
    // Mongoose
    const mongoose = require('mongoose')
    
    /** Data Schema */
    // User Data Schema
    const UserSchema = new mongoose.Schema({
        // User's Name
        name: {
            // User's Firstname
            firstName: { type: String, required: true, min: 4 },
            // User's Lastname
            lastName: { type: String, required: true, min: 4 }
        },
        // User's Address Array[ID]
        address: [{ type: mongoose.Schema.Types.ObjectId, ref: "Address" }] // 1st Correction
    })
    
    /** Data Schema Exports */
    module.exports = mongoose.model('User', UserSchema);
    

    Then next I can straight code like shown below (SOLUTION):-

    User.findById({ _id: userId }) // let's say I've the userId
    .populate('address') // assuming this populate of address works
    .then(async user => {
        // create an array to hold JUST the IDs of Address available
        let addressesID = []
        // transfer all Address's ID
        for(let i = 0; i < (user.address).length; i++) {
            // push available address's ID one by one
            addressesID.push(user.address[i]._id)
        }
        // get all address info available in Address (from DB) 
        const addresses = await Address.find(
            { _id: { $in: addressesID } }
        )
        .populate('state')
        .populate('country')
        .then(address => address)
    })
    .then(all => console.log(all))
    .catch(err => res.json({ err }))
    

    it will then console.log()

    {
        _id: '5fabababababababababab1',
        name: {
            firstName: 'Lalapolalaa',
            lastName: 'Newb'
        },
        address: { // populate happens here
            _id: '5fefefefefefefefefef1',
            addressOne: 'Lot 1, Street 12',
            addressTwo: 'SS 21',
            postcode" : '47500',
            city: 'Subang Jaya',
            state: { // populate happens here
                _id: '5fghghghghghghghghg1',
                name: 'Selangor',
                __v: 0
            },
            country: { // populate happens here
                _id: '5ijijijijijijijijij1',
                name: 'Malaysia',
                __v: 0
            },
            __v: 0
        },
        __v: 0
    }