I found myself using this pattern a lot, where I want to know whether a document exists or not.
const user = await User.findOne({ name: 'Hafez' }).select('_id').lean();
if (user) {
// do stuff
}
It works, but I find it unnecessarily verbose, if this is a common pattern, we could have a function that does the same thing under the hood, and returns true
or false
. Maybe something like
const userExists = await User.exists({ name: 'Hafez' }); // returns true or false
if (userExists) {
// do stuff
}
Meanwhile, I was wondering if .select('_id').lean();
is the most performant way to find if a document exists, some insight here would be greatly appreciated.
P.S:
If this feature is approved, I'd love to contribute by implementing it myself, so feel free to assign it to me.
The other option is to use User.countDocuments({ name: 'Hafez' })
, which will be faster in some cases because less network overhead, but slower in the case where you need to do a full collection scan on a large collection. Feel free to put in a PR for exists()
using your approach and we can experiment with what's faster later.
I had this discussion with a couple of colleagues before, and even asked a question on SO, my current collections have on average 200k docs and growing, so I am favoring the network overhead over the countDocuments approach.
Anyway, I am planning to put in a PR in the weekend.
Great, and the SO answer is very well thought out. The amount of nuance that goes into this question makes me think that an exists()
function is an excellent idea.
In the meantime, I'm using this code just to reduce verbosity:
mongoose.Model.exists = async function (options) {
const result = await this.findOne(options).select("_id").lean();
return result ? true : false;
};
This lets me run this code:
const userExists = await User.exists({ name: 'Hafez' }); // returns true or false
if (userExists) {
// do stuff
}
I thought this method is present in mongoose
API. But not...
@gianlucaparadise I'd recommend using statics instead: https://mongoosejs.com/docs/guide.html#statics . We will add this sooner or later, but for now it's a one liner to add a static or global plugin
I was looking for such shorthand (exists
), currently I'm using:
if (await User.count({query here})) {
// do something
}
So waiting for exists
method to make the code self documenting.
@num8er I am using the same workaround here:
const exists = await User.find({name: 'bar'}).length > 0;
@num8er You are right. Edited my comment
@num8er I am using the same workaround here:
const exists = await User.find({name: 'bar'}).length > 0;
btw this is not good solution.
since mongoose find
uses db.collection.find which returns cursor and waits until it gets all users by requirement.
I recommend to use count
method which uses mongodb's method db.collection.count
which is native or to use findOne
(db.collection.findOne
) which also can be performant in case when name
field is indexed.
since mongoose
find
uses _db.collection.find_ which returns cursor and waits until it gets all users by requirement.
Where did you read this?
since mongoose
find
uses _db.collection.find_ which returns cursor and waits until it gets all users by requirement.Where did you read this?
https://github.com/Automattic/mongoose/blob/master/lib/model.js#L1853 - uses Query
https://github.com/Automattic/mongoose/blob/master/lib/query.js#L112 - Query takes mquery's prototype
https://github.com/aheckmann/mquery/blob/master/lib/mquery.js#L1932 - mquery calls find on db.collection.find through collection wrapper
https://github.com/aheckmann/mquery/blob/master/lib/collection/node.js#L30 - calls cursor.toArray()
@num8er findOne()
is more performant than count()
in this case, unless your network is extremely slow and a couple bytes matter. Because count()
behaves similarly to find()
in that it doesn't stop after it finds a matching doc, so if there's no index then count()
will always do a full collection scan.
@vkarpov15
I agree with You that without index it's not performant.
I always set indexes in my mongoose schemas.
About findOne - yes, it stops, like if we do LIMIT 1
in SQL databases.
But findOne is not self-documenting for such case that's why I've put an example with count
.
About perf of count - who knows how engine will work when we call db.collection.count
in new DB engine.
Cause it depends on how mongodb is optimised in new versions (is it keeping reference count in index?) and how well indexed collection is.
So findOne
vs count
may be ambiguous comparison.
Most helpful comment
In the meantime, I'm using this code just to reduce verbosity:
This lets me run this code: