Faster Mongoose Queries With Lean
By default, Mongoose queries return an instance of the . Documents are much heavier than vanilla JavaScript objects, because they have a lot of internal state for change tracking. Enabling the lean
option tells Mongoose to skip instantiating a full Mongoose document and just give you the POJO.
How much smaller are lean documents? Here’s a comparison.
const schema = new mongoose.Schema({ name: String });
const MyModel = mongoose.model('Test', schema);
await MyModel.create({ name: 'test' });
// Module that estimates the size of an object in memory
const sizeof = require('object-sizeof');
const normalDoc = await MyModel.findOne();
// To enable the `lean` option for a query, use the `lean()` function.
const leanDoc = await MyModel.findOne().lean();
sizeof(normalDoc); // approximately 600
sizeof(leanDoc); // 36, more than 10x smaller!
// In case you were wondering, the JSON form of a Mongoose doc is the same
// as the POJO. This additional memory only affects how much memory your
// Node.js process uses, not how much data is sent over the network.
JSON.stringify(normalDoc).length === JSON.stringify(leanDoc.length); // true
Under the hood, after executing a query, Mongoose converts the query results from POJOs to Mongoose documents. If you turn on the lean
option, Mongoose skips this step.
const normalDoc = await MyModel.findOne();
const leanDoc = await MyModel.findOne().lean();
normalDoc instanceof mongoose.Document; // true
normalDoc.constructor.name; // 'model'
leanDoc instanceof mongoose.Document; // false
leanDoc.constructor.name; // 'Object'
- Change tracking
- Casting and validation
- Getters and setters
- Virtuals
save()
For example, the following code sample shows that the Person
model’s getters and virtuals don’t run if you enable lean
.
Populate works with lean()
. If you use both populate()
and lean()
, the lean
option propagates to the populated documents as well. In the below example, both the top-level ‘Group’ documents and the populated ‘Person’ documents will be lean.
const Group = mongoose.model('Group', new mongoose.Schema({
name: String,
members: [{ type: mongoose.ObjectId, ref: 'Person' }]
}));
name: String
}));
// Initialize data
const people = await Person.create([
{ name: 'Benjamin Sisko' },
{ name: 'Kira Nerys' }
]);
await Group.create({
name: 'Star Trek: Deep Space Nine Characters',
members: people.map(p => p._id)
});
// Execute a lean query
const group = await Group.findOne().lean().populate('members');
group.members[0].name; // 'Benjamin Sisko'
group.members[1].name; // 'Kira Nerys'
// Both the `group` and the populated `members` are lean.
group instanceof mongoose.Document; // false
group.members[0] instanceof mongoose.Document; // false
group.members[1] instanceof mongoose.Document; // false
also works with lean.
// Create models
const groupSchema = new mongoose.Schema({ name: String });
groupSchema.virtual('members', {
ref: 'Person',
localField: '_id',
foreignField: 'groupId'
});
const Group = mongoose.model('Group', groupSchema);
const Person = mongoose.model('Person', new mongoose.Schema({
name: String,
groupId: mongoose.ObjectId
}));
const g = await Group.create({ name: 'DS9 Characters' });
const people = await Person.create([
{ name: 'Benjamin Sisko', groupId: g._id },
{ name: 'Kira Nerys', groupId: g._id }
]);
// Execute a lean query
path: 'members',
options: { sort: { name: 1 } }
});
group.members[0].name; // 'Benjamin Sisko'
group.members[1].name; // 'Kira Nerys'
// Both the `group` and the populated `members` are lean.
group instanceof mongoose.Document; // false
group.members[0] instanceof mongoose.Document; // false
group.members[1] instanceof mongoose.Document; // false
Below is an example of an Express route that is a good candidate for lean()
. This route does not modify the person
doc and doesn’t rely on any Mongoose-specific functionality.
Below is an example of an Express route that should not use lean()
. As a general rule of thumb, GET
routes are good candidates for lean()
in a . On the other hand, PUT
, POST
, etc. routes generally should not use lean()
.
// This route should **not** use `lean()`, because lean means no `save()`.
app.put('/person/:id', function(req, res) {
Person.findOne({ _id: req.params.id }).
then(person => {
assert.ok(person);
Object.assign(person, req.body);
return person.save();
}).
then(person => res.json({ person })).
catch(error => res.json({ error: error.message }));
});
Remember that virtuals do not end up in lean()
query results. Use the mongoose-lean-virtuals plugin to add virtuals to your lean query results.
However, you need to keep in mind that Mongoose does not hydrate lean documents, so this
will be a POJO in virtuals, getters, and default functions.
const schema = new Schema({ name: String });
schema.plugin(require('mongoose-lean-virtuals'));
schema.virtual('lowercase', function() {
this instanceof mongoose.Document; // false
this.name; // Works
this.get('name'); // Crashes because `this` is not a Mongoose document.