76

I have a request which body contains data and _id

What is the better way to implement code that will update if record with _id exists and will create one is there is no one? My code:

var obj = req.body;
Model.findById(obj._id, function(err, data){
    if (!data){
        var model = new Model(obj)
        model.save(function(err, data){
            res.send({method: 'create', error: err, data: data})
        })
    } else {
        Model.findByIdAndUpdate(obj._id, obj, function(){
            res.send({method: 'update', error: err, data: data})
        })
    }

I'm just thinking maybe there is beter way of doing this.

WHITECOLOR
  • 24,996
  • 37
  • 121
  • 181

2 Answers2

125

You can do that with a single upsert:

var obj = req.body;
var id = obj._id;
delete obj._id;
if (id) {
    Model.update({_id: id}, obj, {upsert: true}, function (err) {...});
}

The caveat is that your model's defaults and middleware (if any) will not be applied.

Mongoose 4.x Update

You can now use the setDefaultOnInsert option to also apply defaults if the upsert creates a new document.

Model.update({_id: id}, obj, {upsert: true, setDefaultsOnInsert: true}, cb);
JohnnyHK
  • 305,182
  • 66
  • 621
  • 471
  • The problem is that "obj" contains _id, and in case of update I need to remove it prior update or I get "Mod on _id not allowed" error – WHITECOLOR Nov 13 '12 at 11:54
  • 1
    @WHITECOLOR Good point. Updated the code to remove that before the `update`. – JohnnyHK Nov 13 '12 at 13:33
  • there is the rub. I want newly created (if it doesn't exists) Item to have _id equal to obj._id, If to remove _id from obj before create operation, mongo will create new _id. – WHITECOLOR Nov 14 '12 at 08:19
  • @WHITECOLOR When I tried this, the newly created item did get an `_id` equal to the value of `obj._id` that was saved off in the `id` value used in the query selector. The `update` combines both objects when needing to create a new one. – JohnnyHK Nov 14 '12 at 13:50
  • [Here's a link to the Mongoose Model `update` method, which lists the `upset` option](http://mongoosejs.com/docs/api.html#model_Model.update) – Walter Roman Aug 25 '14 at 23:39
  • @JohnnyHK Out of curiosity, why doesn't it apply the Models Defaults?? Strange – Mark Pieszak - Trilon.io Oct 24 '15 at 17:53
  • This one ignores my created_at and updated_at pre() method – fabian Dec 16 '15 at 10:56
  • @MarkPieszak In Mongoose 4.x, you can now apply defaults during an upsert. See the updated answer. – JohnnyHK Dec 16 '15 at 13:52
  • Yes! I've been waiting for this implementation. I haven't updated to Mongoose 4.x yet but now I definitely am. Thanks @JohnnyHK – Mark Pieszak - Trilon.io Dec 16 '15 at 14:24
  • What if I want to updateOrCreate hundereds of docs at once? Is there a batch way? – Luckylooke Jun 01 '17 at 11:48
  • Using `.update()` will skip `pre` hook validations if you have any. – philosopher Oct 30 '20 at 08:36
  • what if the object I want to update has a list and I need to update that list with a new element? – shinzou Dec 16 '20 at 22:41
2
 var obj = req.body;
 var id = obj._id;
 delete obj._id;
 model.findOneAndUpdate({ _id: id }, obj, {
                     new: true,
                     upsert: true, // Make this update into an upsert
 });
hayelom
  • 31
  • 1