14

Sometimes I need a document to exist in the DB, and am happy to either work with the existing document, or create the document if it's missing, and work with the new one.

This seems like a fairly common use case, but I've looked through the Mongoose docs and I can't find anything.

Mongoose Collection methods like findOneAndUpdate() and update() with upsert: true are about modifying documents - I don't wish to modify the document if it exists, just get a reference to it.

Example: (added for @neillunn) I want to add a User with a reference to a Company whose 'name' is 'foo'. Before that, I'd like to lookup a Company with {name: 'foo'} and create that if it doesn't exist.

Example 2: (added for @neillunn) code I'm using now to handle the example scenario:

// Find or create an an instance and return a cb with a reference to it.
var findOrCreate = function(model, criteria, cb){
    model.update(criteria, criteria, {upsert: true}, function(err, numberAffected, raw){
        if ( ! raw.updatedExisting ) {
            console.log('Created instance')
        } else {
            console.log('Found existing instance')
        }           
        model.findOne(criteria, cb)
    })
}

Note: findOneAndUpdate() won't work because it will try and modify the existing document, and get a 'duplicate key error index'

mikemaccana
  • 110,530
  • 99
  • 389
  • 494
  • Not really clear. You are looking at the right things but why would you just want to essentially "find" if it is there and "modify/upsert" if it is not? The difference seems self explanatory. Perhaps explain your use case better. – Neil Lunn Aug 04 '14 at 11:09
  • So you basically want automatic relations implemented? Being that mongoose doesnt officially or programatically support relations, i think that you will need to execute 2 queries to achieve this. – David Losert Aug 04 '14 at 11:29
  • @Charminbear Not relations, just finding or making as needed. If my example I find/make a company instance because I need to reference `company._id` but that's just the current use case. – mikemaccana Aug 04 '14 at 11:46
  • 1
    @mikemaccana: https://github.com/drudge/mongoose-findorcreate You were searching for something like that? – David Losert Aug 04 '14 at 12:23
  • Very usefull solution for this problem: http://stackoverflow.com/a/7592756/4025963 – David Sep 14 '16 at 15:32

3 Answers3

11

As stated in the comments, there is a plugin for mongoose that does this: http://github.com/drudge/mongoose-findorcreate

This Thread also describes a way to achieve this without plugin. Im just not sure though if it works with mongoose.

Community
  • 1
  • 1
David Losert
  • 4,652
  • 1
  • 25
  • 31
5

Mongoose has an inbuilt findOneAndUpdate function can find and if the data doesn't exist, it will create the data. Basically below is how it's being done:

var modelDoc = new MyModel({ foo: 'bar' });

MyModel.findOneAndUpdate(
    {foo: 'bar'}, // find a document with that filter
    modelDoc, // document to insert when nothing was found
    {upsert: true, new: true, runValidators: true}, // options
    function (err, doc) { // callback
        if (err) {
            // handle error
        } else {
            // handle document
        }
    }
);

The options parameter is essential to make the insert work.`

By setting upsert: true, you command MongoDB to actually add the modelDoc document to the collection if no result was found by the {foo: 'bar'}filter.

The new: true option changes what is returned in the callback function. If it is false, doc will contain the document before updating (or null before inserting). If it is true, doc contains the document after updating or creating. While false is the default option, I guess most people would actually prefer it to be true.

The runValidators: true option must be set if you want Mongoose to run the validators. By default, these will not be executed when using findOneAndUpdate().

mikemaccana
  • 110,530
  • 99
  • 389
  • 494
imin
  • 4,504
  • 13
  • 56
  • 103
  • From the docs: "When using the upsert: true option with the findOneAndUpdate() method, and **not using a unique index on the query field(s)**, multiple instances of a findOneAndUpdate() operation with similar query field(s) **could result in duplicate documents being inserted in certain circumstances**." – AlexPi Sep 14 '21 at 21:05
1

Very useful solution here: https://stackoverflow.com/a/7592756/4025963:

var Counters = new Schema({
  _id: String,
  next: Number     
});

Counters.statics.findAndModify = function (query, sort, doc, options, callback) {
  return this.collection.findAndModify(query, sort, doc, options, callback);
};

var Counter = mongoose.model('counters', Counters);

Counter.findAndModify({ _id: 'messagetransaction' }, [], { $inc: { next: 1 } }, {}, function (err, counter) {
  if (err) throw err;
  console.log('updated, counter is ' + counter.next);
});

Even more interesting when used to promises:

Counters.statics.findAndModify =
    function findAndModify(query, sort, update, options, callback) {
        const promised = q.nbind(this.collection.findAndModify, this.collection);
        return promised(query || {}, sort || [], update || {}, options, callback);
    };
Community
  • 1
  • 1
David
  • 2,741
  • 16
  • 26