Mongoose .populate() is not working with node.js app

First post here, so hello to everyone!

I’m having trouble with mongoose populate because it’s apparently doing nothing. I’m going straight to the code.

Here are my models:

Space

const spaceSchema = Schema(
	{
		user: {
			type: Schema.Types.ObjectId,
			required: true,
			ref: "User",
		},
		name: {
			type: String,
			required: true,
		},
		days: [{ type: Schema.Types.ObjectId, refPath: "Day" }],
	},
	{
		versionKey: false,
	        timestamps: true 
        },
);

const Space = mongoose.model("Space", spaceSchema);

module.exports = Space;

Day

const daySchema = Schema(
	{
		space: { type: Schema.Types.ObjectId, refPath: "Space" },
		date: {
			type: String,
			required: true,
		},
		water: {
			type: Boolean,
		},
		fertilizer: {
			type: Boolean,
		},
		transplant: {
			type: Boolean,
		},
		comment: {
			type: String,
		},
	},
	{
		versionKey: false,
	        timestamps: true 
        },
);

const Day = mongoose.model("Day", daySchema);

module.exports = Day;

And the part of the controller where I have the getSpace route (/api/spaces/:id):

const getSpace = asyncHandler(async (req, res) => {
	const space = await Space.findById(req.params.id).populate("days");
	res.status(200).json(space);
});

And this is the result I get:

{
    "_id": "63580115978dbf8f2f5a7a50",
    "user": "63501ab84613855834daa4ef",
    "name": "spaceName",
    "days": []
}

I tried many things, but all the results were the same.

Let me know if you need any other part of the code.

Thank you all :smiley:

I expect the result to look something like this

    "space": {
        "_id": "63580115978dbf8f2f5a7a50",
        "user": "63501ab84613855834daa4ef",
        "name": "spaceName",
        "days": [
            {
                "_id": "63581af565aed8cad3210046",
                "space": "63580115978dbf8f2f5a7a50",
                "date": "29/10/2022",
                "water": true,
                "fertilizer": true,
                "transplant": false,
                "comment": "This is a comment.",
                "createdAt": ...,
                "updatedAt": ...
            },
            {
                "_id": "63581af565aed8cad3210046",
                "space": "63580115978dbf8f2f5a7a50",
                "date": "29/10/2022",
                "water": false,
                "fertilizer": false,
                "transplant": true,
                "comment": "This is a comment.",
                "createdAt": ...,
                "updatedAt": ...
            }
        ]
    }

refPath doesn’t mean, what you might think it does. What you need here is a simple ref.
So, just change refPath to ref in your schemas and your example should be working as you expect.

But, there is a second thing. You have redundant references: days refer to spaces and spaces to days. Generally you don’t need to refer to each other in both ways (unless in your specific use case you do). For many-to-one or many-to-many relations there are Populate Virtuals. You can read up on it in the Mongoose documentation.

So I would get rid of the days field in the Space schema, and replace it with a populate virtual:

const spaceSchema = Schema(
	{
		user: {
			type: Schema.Types.ObjectId,
			required: true,
			ref: "User",
		},
		name: {
			type: String,
			required: true,
		},
	},
	{
		versionKey: false,
		timestamps: true,
	},
);

spaceSchema.virtual('days', {
	ref: 'Day',
	localField: '_id',
	foreignField: 'space',
});

const Space = mongoose.model("Space", spaceSchema);

module.exports = Space;
1 Like

Hi pepkin88,

Thanks for your answer :smiley:

I tried this populate virtual, but it’s returning me this now:

{
    "_id": "63580b9f6d2b99c33923c876",
    "user": "63501ab84613855834daa4ef",
    "name": "balco",
    "createdAt": "2022-10-25T16:15:27.304Z",
    "updatedAt": "2022-10-25T16:15:27.304Z"
}

There are no days in my answer :sweat_smile:

I also changed refPath to ref but no luck.

I also tried another way (I’m not so wure if it’s better or worse than using populate) but I got the answer I wanted.
I used this:

const getSpace = asyncHandler(async (req, res) => {
	const space = await Space.findById(req.params.spaceId);
	space.days = await Day.find({ space: req.params.spaceId });
	res.status(200).json(space);
});

And answer is what I wanted :partying_face:

Thanks for your answer anyway :mechanical_arm:

1 Like

I’m using the latest version of mongodb and mongoose and populate() is working fine…

mongodb: 6.3.0
mongoose:8.0.1

This is from my app.js file


This is from my task model file

const task_schema = new mongoose.Schema({
    name:{
        type:String,
        trim:true,
        required:true
    },
    desc:{
        type:String,
        trim:true,
        required:true
    },
    completed:{
        type:Boolean,
        trim:true,
        default:false
    },
    owner:{
        type: mongoose.Schema.Types.ObjectId,
        required:true,
        ref:'user_collection'
    }
});

This is from my task router file

//creating a task...
router.post('/tasks',auth, async (req, res) => {
    const task  = task_model({
        ...req.body,
        owner: req.user._id
    })
    
    console.log(`Url tasks is hit with request....`);
    try {
        const savedTask = await task.save();
        res.send(savedTask);
    } catch (err) {
        res.send(500).send(err);
    }

})

This from auth middleware file

const auth = async (req, res, next) => {
    try {
        const token = req.header('Authorization').replace('Bearer ','');
        const decoded = jwt.verify(token,'secret123');
        const user = await user_model.findOne({_id: decoded._id,'tokens.token':token});

        if(!user){
            throw new Error();
        }
        
        req.token = token;
        req.user = user;
        next();
    } catch (err) {
        res.status(401).send(`You are not authorized ${err}`);
    }
}

Hope it helps…