Introduction to Mongoose Populate

Nov 12, 2019

In Mongoose, populate lets you pull in referenced documents from another collection. Populate is similar to a left outer join in SQL, but the difference is that populate happens in your Node.js application rather than on the database server. Mongoose executes a separate query under the hood to load the referenced documents.

Basic Populate

Suppose you have two Mongoose models: Movie and Person. Movie documents have a director and an array of actors.

const Person = mongoose.model('Person', mongoose.Schema({
  name: String
}));

// `ref` tells Mongoose populate what model to query
const Movie = mongoose.model('Movie', mongoose.Schema({
  title: String,
  director: {
    type: mongoose.ObjectId,
    ref: 'Person'
  },
  actors: [{
    type: mongoose.ObjectId,
    ref: 'Person'
  }]
}));

Mongoose queries have a populate() function that lets you load a movie and its corresponding director and actors in one line:

const people = await Person.create([
  { name: 'James Cameron' },
  { name: 'Arnold Schwarzenegger' },
  { name: 'Linda Hamilton' }
]);
await Movie.create({
  title: 'Terminator 2',
  director: people[0]._id,
  actors: [people[1]._id, people[2]._id]
});

// Load just the movie's director
let movie = await Movie.findOne().populate('director');
movie.director.name; // 'James Cameron'
movie.actors[0].name; // undefined

// Load both the director and the actors
movie = await Movie.findOne().populate('director').populate('actors');
movie.director.name; // 'James Cameron'
movie.actors[0].name; // 'Arnold Schwarzenegger'
movie.actors[1].name; // 'Linda Hamilton'

Populate On Existing Documents

Mongoose documents also have a populate() function. Given an existing movie document, you can populate() any number of paths. Just remember to call Document#execPopulate() to actually execute the populate() call.

// Load just the movie's director
let movie = await Movie.findOne();
movie.director.name; // undefined
movie.actors[0].name; // undefined

// Populate the director
await movie.populate('director').execPopulate();
movie.director.name; // 'James Cameron'
movie.actors[0].name; // undefined

// Populate the actors
await movie.populate('actors').execPopulate();
movie.director.name; // 'James Cameron'
movie.actors[0].name; // 'Arnold Schwarzenegger'
movie.actors[1].name; // 'Linda Hamilton'

Edge Cases

If you're populating a single document and the referenced document doesn't exist, Mongoose will set the populated property to null.

await Person.deleteOne({ name: 'James Cameron' });

const movie = await Movie.findOne().populate('director');
movie.director; // null

If you're populating an array and one of the referenced documents doesn't exist, Mongoose will filter that value out of the array by default, returning a shorter array. You can override this with the retainNullValues option.

await Person.deleteOne({ name: 'Arnold Schwarzenegger' });

let movie = await Movie.findOne().populate('actors');
movie.actors.length; // 1
movie.actors[0].name; // 'Linda Hamilton'

// Set `retainNullValues` option to insert `null` for
// missing documents in the array
movie = await Movie.findOne().populate({
  path: 'actors',
  options: { retainNullValues: true }
});
movie.actors.length; // 2
movie.actors[0]; // null
movie.actors[1].name; // 'Linda Hamilton'

More Mongoose Tutorials