Introduction to Mongoose Populate
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'