mongoose-autopopulate

Usage

The mongoose-autopopulate module exposes a single function that you can pass to Mongoose schema's plugin() function.

const schema = new mongoose.Schema({
  populatedField: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'ForeignModel',
    // The below option tells this plugin to always call `populate()` on
    // `populatedField`
    autopopulate: true
  }
});
schema.plugin(require('mongoose-autopopulate'));

mongoose-autopopulate plugin

It supports an autopopulate option in schemas

Suppose you have two collections, "people" and "bands". The People model looks like this:

var personSchema = new Schema({ name: String, birthName: String });
Person = mongoose.model('people', personSchema, 'people');

Suppose your "people" collection has one document:

{
name: 'Axl Rose',
birthName: 'William Bruce Rose, Jr.',
_id: '54ef3f374849dcaa649a3abc'
};

And your "bands" collection has one document:

{
_id: '54ef3f374849dcaa649a3abd',
name: "Guns N' Roses",
lead: '54ef3f374849dcaa649a3abc',
members: ['54ef3f374849dcaa649a3abc']
}

You can set the autopopulate option for the lead field. This means that, every time you call find() or findOne(), mongoose-autopopulate will automatically call .populate('lead') for you.

var bandSchema = new Schema({
  name: String,
  lead: { type: ObjectId, ref: 'people', autopopulate: true }
});
bandSchema.plugin(autopopulate);

var Band = mongoose.model('band3', bandSchema, 'bands');
Band.findOne({ name: "Guns N' Roses" }, function(error, doc) {
  assert.ifError(error);
  assert.equal('Axl Rose', doc.lead.name);
  assert.equal('William Bruce Rose, Jr.', doc.lead.birthName);
  done();
});

It supports document arrays

mongoose-autopopulate also works on arrays.

var bandSchema = new Schema({
  name: String,
  members: [{ type: ObjectId, ref: 'people', autopopulate: true }]
});
bandSchema.plugin(autopopulate);

var Band = mongoose.model('band4', bandSchema, 'bands');
Band.findOne({ name: "Guns N' Roses" }, function(error, doc) {
  assert.ifError(error);
  assert.equal('Axl Rose', doc.members[0].name);
  assert.equal('William Bruce Rose, Jr.', doc.members[0].birthName);
  done();
});

It can specify an options argument

Advanced users of populate() may want to specify additional options, such as selecting fields. If you set the autopopulate option to an object, mongoose-autopopulate will merge the object into populate options. The findOne() below is equivalent to Band.findOne({ name: "Guns N' Roses" }).populate({ path: 'lead', select: 'name });

var bandSchema = new Schema({
  name: String,
  lead: { type: ObjectId, ref: 'people', autopopulate: { select: 'name' } }
});
bandSchema.plugin(autopopulate);

var Band = mongoose.model('band5', bandSchema, 'bands');
Band.findOne({ name: "Guns N' Roses" }, function(error, doc) {
  assert.ifError(error);
  assert.equal('Axl Rose', doc.lead.name);
  assert.ok(!doc.lead.birthName);
  done();
});

It can specify a function that returns options

You can also set the autopopulate option to be a function. Then mongoose-autopopulate will call the function with the query object as the context and use the return value. The below populate() uses the same options as the previous example.

var numCalls = 0;
var optionsFunction = function() {
  ++numCalls;
  return { select: 'name' };
};

var bandSchema = new Schema({
  name: String,
  lead: { type: ObjectId, ref: 'people', autopopulate: optionsFunction }
});
bandSchema.plugin(autopopulate);

var Band = mongoose.model('band6', bandSchema, 'bands');
Band.find({ name: "Guns N' Roses" }, function(error, docs) {
  assert.ifError(error);
  assert.equal(1, docs.length);
  assert.equal(1, numCalls);
  var doc = docs[0];
  assert.equal('Axl Rose', doc.lead.name);
  assert.ok(!doc.lead.birthName);
  done();
});

It can disable autopopulate for individual queries

If you set the autopopulate option to false on a query, autopopulate will be disabled. This is handy if you want to autopopulate by default, but opt-out for special cases.

var bandSchema = new Schema({
  name: String,
  lead: { type: ObjectId, ref: 'people', autopopulate: true }
});
bandSchema.plugin(autopopulate);

var Band = mongoose.model('band7', bandSchema, 'bands');
Band.findOne({ name: "Guns N' Roses" }, {}, { autopopulate: false }, function(error, doc) {
  assert.ifError(error);
  assert.ok(doc.lead instanceof mongoose.Types.ObjectId);
  assert.ok(!doc.populated('lead'));
  done();
});

It can disable autopopulate in populate() options

Say you have a model User that has the autopopulate plugin and you're populating users from a different model. To disable autopopulate, you need to set autopopulate: false as a populate option, not a query option.

const userSchema = new Schema({
  name: String,
  friends: [{
    type: Schema.Types.ObjectId,
    ref: 'User',
    autopopulate: { maxDepth: 2 }
  }]
});
userSchema.plugin(autopopulate);

const responseSchema = new Schema({
  user: {
    type: Schema.Types.ObjectId,
    ref: 'User'
  }
});

const User = mongoose.model('User', userSchema);
const Response = mongoose.model('Response', responseSchema);

return co(function*() {
  const axl = new User({ name: 'Axl' });
  const slash = new User({ name: 'Slash', friends: [axl._id] });
  axl.friends.push(slash._id);

  yield [axl.save(), slash.save()];
  let r = yield Response.create({ user: axl._id });

  r = yield Response.findById(r._id).
    // Because `User` is the foreign model, you need to disable autopopulate
    // in the populate options below, not the query options
    populate({ path: 'user', options: { autopopulate: false } });
});

It requires an option to work with lean

Setting the Mongoose lean option will disable autopopulate for all paths, unless you add autopulate: true to your lean option.

// acquit:ignore:start
return co(function*() {
  // acquit:ignore:end
  let band = yield Band.findOne().lean();
  // Won't autopopulate because `lean()` is set
  assert.ok(band.lead instanceof mongoose.Types.ObjectId);

  // To turn on `autopopulate` with lean, use `lean({ autopulate: true })`
  band = yield Band.findOne().lean({ autopopulate: true });
  assert.equal(band.lead.name, 'Axl Rose');

It can limit the depth using maxDepth

Recursive populate can lead to messy infinite recursion, so this plugin supports a maxDepth option that limits how deep recursive population will go. The maxDepth option is 10 by default

return co(function*() {
  const accountSchema = new mongoose.Schema({
    name: String,
    friends: [{
      type: mongoose.Schema.Types.ObjectId,
      ref: 'Account',
      // This is a recursive relationship, `friends` points to a list
      // of accounts. If we didn't limit the depth, this would result
      // in infinite recursion!
      autopopulate: { maxDepth: 2 }
    }]
  });
  accountSchema.plugin(autopopulate);

  const Account = mongoose.model('Account', accountSchema);

  const axl = new Account({ name: 'Axl' });
  const slash = new Account({ name: 'Slash', friends: [axl._id] });
  axl.friends.push(slash._id);

  yield axl.save();
  yield slash.save();

  const doc = yield Account.findById(axl._id);

  assert.equal(doc.friends[0].name, 'Slash');
  assert.equal(doc.friends[0].friends[0].name, 'Axl');
  // Only populate 2 levels deep, 3rd level will still be an `_id`
  assert.equal(doc.friends[0].friends[0].friends[0].toString(),
    slash._id.toHexString());
});

Changelog

0.9.0 / 2018-11-08

0.8.2 / 2018-10-10

0.8.1 / 2018-09-02

0.8.0 / 2018-07-01

0.7.0 / 2018-05-10

0.5.0 / 2016-11-29

0.4.0 / 2015-09-03

0.3.0 / 2015-07-30

0.2.0 / 2015-06-27