Mongoose: [Feature] Model.exists(query)

Created on 16 Aug 2018  路  15Comments  路  Source: Automattic/mongoose

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.

new feature

Most helpful comment

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
}

All 15 comments

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.

Was this page helpful?
0 / 5 - 0 ratings