Mongoose’s Model.Populate()

A Tutorial We Newbs Can Understand

Nick Nauert
4 min readSep 2, 2017

EDIT MAR 2024: Hi. I have no clue whether the information below is accurate anymore. It’d be pretty cool if it was. But I haven’t touched Mongoose in about 6 years. No guarantees on a cringe free experience either. Take care.

If you’ve looked at any of my few posts, you know I’m a junior programmer in training. If you haven’t looked at any of my posts, I’m a junior programmer in training. I’m attending The Iron Yard Charleston, and we’re about neck deep in learning about databases, and getting deeper. The rising of the tide is made worse by certain technical documentation not always being super friendly to newbs like myself. I’m looking at you, Mongoose. You beautiful, opaque thing you.

Did you read the documentationnnnssss?

My latest issue was with using multiple collections for a social media-esque site. One collection for users, and one for posts. In my research before doing any coding, I stumbled upon Model.populate(), a Mongoose method that you can use to essentially link documents across collections. This allows you to have a schema for each of them, and generally keep things all nice and tidy.

It should have been easy, and honestly, it is! But most tutorials I read on it left out one tiny little step that cause me about five hours of wasted time. This is almost certainly due to my inexperience, so I’m going to go through the very simple baby-steps in hopes of helping another fledgling coder like me avoid wasted hours.

Step 1: Make your schemas

You need a schema for each collection. One for the users, and one for the posts those users are going to make.

const UserSchema = new mongoose.Schema({
username: String,
posts: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Post'
}]
})
const PostSchema = new mongoose.Schema({
content: String,
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
})
const Post = mongoose.model('Post', PostSchema, 'posts');
const User = mongoose.model('User', UserSchema, 'users');
module.exports = {
User, Post,
}

The properties that we want to use .populate() on are properties that have a type of mongoose.Schema.Types.ObjectId. This tells Mongoose “Hey, I’m gonna be referencing other documents from other collections”. The next part of that property is the ref. The ref tells Mongoose “Those docs are going to be in the ___ collection.”

So in our User schema, we reference the Post collection, because we want the user to be tied to the things they post, and we want to be able to easily access those posts without having to create more queries.

And with that, our schemas are set.

Step 2: Correctly Creating Users And Posts

For the docs to properly connect when you populate later on, you need to understand the one, super simple thing that no one ever explicitly stated while I was scouring the internet for help on this. After linking other collections in your schema using the appropriate type and ref, your actual stored data for that property will be another document’s _id. It will be stored as a string. This also works for an array of _ids.

So while your schema says this:

const UserSchema = new mongoose.Schema({
username: String,
posts: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Post'
}]
})

Your actual stored property should read something like this:

{
_id: 59ab1c92ea84486fb4ba9f28,
username: JD,
posts: [
"59ab1b43ea84486fb4ba9ef0",
"59ab1b43ea84486fb4ba9ef1"
]
}

Keep in mind that this is your stored document. We have not called .populate() on it yet. Once it is called, it will go to the appropriate collection, search for those two _ids, and return your user, but now with an array of her actual posts. Let’s do that now.

Step 3: Implementing .Populate()

Here’s the function:

function getUserWithPosts(username){
return User.findOne({ username: username })
.populate('posts').exec((err, posts) => {
console.log("Populated User " + posts);
})
}

.populate() needs a query to attach itself to, so we are using User.findOne() to find a user who matches the username we provide in the argument. This returns our user document. This is when .populate() takes over. You’ll notice I am providing ‘posts’ to our .populate(). By providing the ‘posts’ argument, we’ve told .populate() what property in our user document we want it to work with. Calling .exec() just executes something once .populate() has done it’s thing. The log prints this:

{ 
_id: 59ab1c92ea84486fb4ba9f28,
username: 'JD',
posts:
[
{
_id: 59ab1b43ea84486fb4ba9ef0,
content: "Is it dark out?"
},{
_id: 59ab1b43ea84486fb4ba9ef1,
content: "Hey anyone got a cup of sugar?"
}
]
}

And like magic, we have created a unified object using 2 schemas, 2 models, and 2 collections. All of the steps are important of course, but the thing that no other site made explicitly clear was that after setting up the ground work, you have to make sure you are pushing _ids into the field you will need populated later.

Hey you’re not so bad after all.

--

--