[javascript] How do I update/upsert a document in Mongoose?

Perhaps it's the time, perhaps it's me drowning in sparse documentation and not being able to wrap my head around the concept of updating in Mongoose :)

Here's the deal:

I have a contact schema and model (shortened properties):

var mongoose = require('mongoose'),
    Schema = mongoose.Schema;

var mongooseTypes = require("mongoose-types"),
    useTimestamps = mongooseTypes.useTimestamps;


var ContactSchema = new Schema({
    phone: {
        type: String,
        index: {
            unique: true,
            dropDups: true
        }
    },
    status: {
        type: String,
        lowercase: true,
        trim: true,
        default: 'on'
    }
});
ContactSchema.plugin(useTimestamps);
var Contact = mongoose.model('Contact', ContactSchema);

I receive a request from the client, containing the fields I need and use my model thusly:

mongoose.connect(connectionString);
var contact = new Contact({
    phone: request.phone,
    status: request.status
});

And now we reach the problem:

  1. If I call contact.save(function(err){...}) I'll receive an error if the contact with the same phone number already exists (as expected - unique)
  2. I can't call update() on contact, since that method does not exist on a document
  3. If I call update on the model:
    Contact.update({phone:request.phone}, contact, {upsert: true}, function(err{...})
    I get into an infinite loop of some sorts, since the Mongoose update implementation clearly doesn't want an object as the second parameter.
  4. If I do the same, but in the second parameter I pass an associative array of the request properties {status: request.status, phone: request.phone ...} it works - but then I have no reference to the specific contact and cannot find out its createdAt and updatedAt properties.

So the bottom line, after all I tried: given a document contact, how do I update it if it exists, or add it if it doesn't?

Thanks for your time.

This question is related to javascript mongodb node.js mongoose

The answer is


You can simply update the record with this and get the updated data in response

router.patch('/:id', (req, res, next) => {
    const id = req.params.id;
    Product.findByIdAndUpdate(id, req.body, {
            new: true
        },
        function(err, model) {
            if (!err) {
                res.status(201).json({
                    data: model
                });
            } else {
                res.status(500).json({
                    message: "not found any relative data"
                })
            }
        });
});

There is a bug introduced in 2.6, and affects to 2.7 as well

The upsert used to work correctly on 2.4

https://groups.google.com/forum/#!topic/mongodb-user/UcKvx4p4hnY https://jira.mongodb.org/browse/SERVER-13843

Take a look, it contains some important info

UPDATED:

It doesnt mean upsert does not work. Here is a nice example of how to use it:

User.findByIdAndUpdate(userId, {online: true, $setOnInsert: {username: username, friends: []}}, {upsert: true})
    .populate('friends')
    .exec(function (err, user) {
        if (err) throw err;
        console.log(user);

        // Emit load event

        socket.emit('load', user);
    });

This coffeescript works for me with Node - the trick is that the _id get's stripped of its ObjectID wrapper when sent and returned from the client and so this needs to be replaced for updates (when no _id is provided, save will revert to insert and add one).

app.post '/new', (req, res) ->
    # post data becomes .query
    data = req.query
    coll = db.collection 'restos'
    data._id = ObjectID(data._id) if data._id

    coll.save data, {safe:true}, (err, result) ->
        console.log("error: "+err) if err
        return res.send 500, err if err

        console.log(result)
        return res.send 200, JSON.stringify result

app.put('url', function(req, res) {

        // use our bear model to find the bear we want
        Bear.findById(req.params.bear_id, function(err, bear) {

            if (err)
                res.send(err);

            bear.name = req.body.name;  // update the bears info

            // save the bear
            bear.save(function(err) {
                if (err)
                    res.send(err);

                res.json({ message: 'Bear updated!' });
            });

        });
    });

Here is a better approach to solving the update method in mongoose, you can check Scotch.io for more details. This definitely worked for me!!!


After reading the posts above, I decided to use this code:

    itemModel.findOne({'pid':obj.pid},function(e,r){
        if(r!=null)
        {
             itemModel.update({'pid':obj.pid},obj,{upsert:true},cb);
        }
        else
        {
            var item=new itemModel(obj);
            item.save(cb);
        }
    });

if r is null, we create new item. Otherwise, use upsert in update because update does not create new item.


You were close with

Contact.update({phone:request.phone}, contact, {upsert: true}, function(err){...})

but your second parameter should be an object with a modification operator for example

Contact.update({phone:request.phone}, {$set: { phone: request.phone }}, {upsert: true}, function(err){...})

Following Traveling Tech Guy's answer, which already awesome, we can create a plugin and attach it to mongoose once we initialise it so that .upsert() will be available on all models.

plugins.js

export default (schema, options) => {
  schema.statics.upsert = async function(query, data) {
    let record = await this.findOne(query)
    if (!record) {
      record = new this(data)
    } else {
      Object.keys(data).forEach(k => {
        record[k] = data[k]
      })
    }
    return await record.save()
  }
}

db.js

import mongoose from 'mongoose'

import Plugins from './plugins'

mongoose.connect({ ... })
mongoose.plugin(Plugins)

export default mongoose

Then you can do something like User.upsert({ _id: 1 }, { foo: 'bar' }) or YouModel.upsert({ bar: 'foo' }, { value: 1 }) whenever you want.


I just burned a solid 3 hours trying to solve the same problem. Specifically, I wanted to "replace" the entire document if it exists, or insert it otherwise. Here's the solution:

var contact = new Contact({
  phone: request.phone,
  status: request.status
});

// Convert the Model instance to a simple object using Model's 'toObject' function
// to prevent weirdness like infinite looping...
var upsertData = contact.toObject();

// Delete the _id property, otherwise Mongo will return a "Mod on _id not allowed" error
delete upsertData._id;

// Do the upsert, which works like this: If no Contact document exists with 
// _id = contact.id, then create a new doc using upsertData.
// Otherwise, update the existing doc with upsertData
Contact.update({_id: contact.id}, upsertData, {upsert: true}, function(err{...});

I created an issue on the Mongoose project page requesting that info about this be added to the docs.


If generators are available it becomes even more easier:

var query = {'username':this.req.user.username};
this.req.newData.username = this.req.user.username;
this.body = yield MyModel.findOneAndUpdate(query, this.req.newData).exec();

to build on what Martin Kuzdowicz posted above. I use the following to do an update using mongoose and a deep merge of json objects. Along with the model.save() function in mongoose this allows mongoose to do a full validation even one that relies on other values in the json. it does require the deepmerge package https://www.npmjs.com/package/deepmerge. But that is a very light weight package.

var merge = require('deepmerge');

app.put('url', (req, res) => {

    const modelId = req.body.model_id;

    MyModel.findById(modelId).then((model) => {
        return Object.assign(model, merge(model.toObject(), req.body));
    }).then((model) => {
        return model.save();
    }).then((updatedModel) => {
        res.json({
            msg: 'model updated',
            updatedModel
        });
    }).catch((err) => {
        res.send(err);
    });
});

this worked for me.

_x000D_
_x000D_
app.put('/student/:id', (req, res) => {_x000D_
    Student.findByIdAndUpdate(req.params.id, req.body, (err, user) => {_x000D_
        if (err) {_x000D_
            return res_x000D_
                .status(500)_x000D_
                .send({error: "unsuccessful"})_x000D_
        };_x000D_
        res.send({success: "success"});_x000D_
    });_x000D_
_x000D_
});
_x000D_
_x000D_
_x000D_


//Here is my code to it... work like ninj

router.param('contractor', function(req, res, next, id) {
  var query = Contractors.findById(id);

  query.exec(function (err, contractor){
    if (err) { return next(err); }
    if (!contractor) { return next(new Error("can't find contractor")); }

    req.contractor = contractor;
    return next();
  });
});

router.get('/contractors/:contractor/save', function(req, res, next) {

    contractor = req.contractor ;
    contractor.update({'_id':contractor._id},{upsert: true},function(err,contractor){
       if(err){ 
            res.json(err);
            return next(); 
            }
    return res.json(contractor); 
  });
});


--

I'm the maintainer of Mongoose. The more modern way to upsert a doc is to use the Model.updateOne() function.

await Contact.updateOne({
    phone: request.phone
}, { status: request.status }, { upsert: true });

If you need the upserted doc, you can use Model.findOneAndUpdate()

const doc = await Contact.findOneAndUpdate({
    phone: request.phone
}, { status: request.status }, { upsert: true });

The key takeaway is that you need to put the unique properties in the filter parameter to updateOne() or findOneAndUpdate(), and the other properties in the update parameter.

Here's a tutorial on upserting documents with Mongoose.


Mongoose now supports this natively with findOneAndUpdate (calls MongoDB findAndModify).

The upsert = true option creates the object if it doesn't exist. defaults to false.

var query = {'username': req.user.username};
req.newData.username = req.user.username;

MyModel.findOneAndUpdate(query, req.newData, {upsert: true}, function(err, doc) {
    if (err) return res.send(500, {error: err});
    return res.send('Succesfully saved.');
});

In older versions Mongoose does not support these hooks with this method:

  • defaults
  • setters
  • validators
  • middleware

For anyone arriving here still looking for good a solution for "upserting" with hooks support, this is what I have tested and working. It still requires 2 DB calls but is much more stable than anything I've tried in a single call.

// Create or update a Person by unique email.
// @param person - a new or existing Person
function savePerson(person, done) {
  var fieldsToUpdate = ['name', 'phone', 'address'];

  Person.findOne({
    email: person.email
  }, function(err, toUpdate) {
    if (err) {
      done(err);
    }

    if (toUpdate) {
      // Mongoose object have extra properties, we can either omit those props
      // or specify which ones we want to update.  I chose to update the ones I know exist
      // to avoid breaking things if Mongoose objects change in the future.
      _.merge(toUpdate, _.pick(person, fieldsToUpdate));
    } else {      
      toUpdate = person;
    }

    toUpdate.save(function(err, updated, numberAffected) {
      if (err) {
        done(err);
      }

      done(null, updated, numberAffected);
    });
  });
}

I created a StackOverflow account JUST to answer this question. After fruitlessly searching the interwebs I just wrote something myself. This is how I did it so it can be applied to any mongoose model. Either import this function or add it directly into your code where you are doing the updating.

function upsertObject (src, dest) {

  function recursiveFunc (src, dest) {
    _.forOwn(src, function (value, key) {
      if(_.isObject(value) && _.keys(value).length !== 0) {
        dest[key] = dest[key] || {};
        recursiveFunc(src[key], dest[key])
      } else if (_.isArray(src) && !_.isObject(src[key])) {
          dest.set(key, value);
      } else {
        dest[key] = value;
      }
    });
  }

  recursiveFunc(src, dest);

  return dest;
}

Then to upsert a mongoose document do the following,

YourModel.upsert = function (id, newData, callBack) {
  this.findById(id, function (err, oldData) {
    if(err) {
      callBack(err);
    } else {
      upsertObject(newData, oldData).save(callBack);
    }
  });
};

This solution may require 2 DB calls however you do get the benefit of,

  • Schema validation against your model because you are using .save()
  • You can upsert deeply nested objects without manual enumeration in your update call, so if your model changes you do not have to worry about updating your code

Just remember that the destination object will always override the source even if the source has an existing value

Also, for arrays, if the existing object has a longer array than the one replacing it then the values at the end of the old array will remain. An easy way to upsert the entire array is to set the old array to be an empty array before the upsert if that is what you are intending on doing.

UPDATE - 01/16/2016 I added an extra condition for if there is an array of primitive values, Mongoose does not realize the array becomes updated without using the "set" function.


I needed to update/upsert a document into one collection, what I did was to create a new object literal like this:

notificationObject = {
    user_id: user.user_id,
    feed: {
        feed_id: feed.feed_id,
        channel_id: feed.channel_id,
        feed_title: ''
    }
};

composed from data that I get from somewhere else in my database and then call update on the Model

Notification.update(notificationObject, notificationObject, {upsert: true}, function(err, num, n){
    if(err){
        throw err;
    }
    console.log(num, n);
});

this is the ouput that I get after running the script for the first time:

1 { updatedExisting: false,
    upserted: 5289267a861b659b6a00c638,
    n: 1,
    connectionId: 11,
    err: null,
    ok: 1 }

And this is the output when I run the script for the second time:

1 { updatedExisting: true, n: 1, connectionId: 18, err: null, ok: 1 }

I'm using mongoose version 3.6.16


Very elegant solution you can achieve by using chain of Promises:

app.put('url', (req, res) => {

    const modelId = req.body.model_id;
    const newName = req.body.name;

    MyModel.findById(modelId).then((model) => {
        return Object.assign(model, {name: newName});
    }).then((model) => {
        return model.save();
    }).then((updatedModel) => {
        res.json({
            msg: 'model updated',
            updatedModel
        });
    }).catch((err) => {
        res.send(err);
    });
});

I just came back to this issue after a while, and decided to publish a plugin based on the answer by Aaron Mast.

https://www.npmjs.com/package/mongoose-recursive-upsert

Use it as a mongoose plugin. It sets up a static method which will recursively merge the object passed in.

Model.upsert({unique: 'value'}, updateObject});

Here's the simplest way to create/update while also calling the middleware and validators.

Contact.findOne({ phone: request.phone }, (err, doc) => {
    const contact = (doc) ? doc.set(request) : new Contact(request);

    contact.save((saveErr, savedContact) => {
        if (saveErr) throw saveErr;
        console.log(savedContact);
    });
})

User.findByIdAndUpdate(req.param('userId'), req.body, (err, user) => {
    if(err) return res.json(err);

    res.json({ success: true });
});

No other solution worked for me. I'm using a post request and updating data if found else insert it, also _id is sent with the request body that's needs to be removed.

router.post('/user/createOrUpdate', function(req,res){
    var request_data = req.body;
    var userModel = new User(request_data);
    var upsertData = userModel.toObject();
    delete upsertData._id;

    var currentUserId;
    if (request_data._id || request_data._id !== '') {
        currentUserId = new mongoose.mongo.ObjectId(request_data._id);
    } else {
        currentUserId = new mongoose.mongo.ObjectId();
    }

    User.update({_id: currentUserId}, upsertData, {upsert: true},
        function (err) {
            if (err) throw err;
        }
    );
    res.redirect('/home');

});

Examples related to javascript

need to add a class to an element How to make a variable accessible outside a function? Hide Signs that Meteor.js was Used How to create a showdown.js markdown extension Please help me convert this script to a simple image slider Highlight Anchor Links when user manually scrolls? Summing radio input values How to execute an action before close metro app WinJS javascript, for loop defines a dynamic variable name Getting all files in directory with ajax

Examples related to mongodb

Server Discovery And Monitoring engine is deprecated Avoid "current URL string parser is deprecated" warning by setting useNewUrlParser to true MongoNetworkError: failed to connect to server [localhost:27017] on first connect [MongoNetworkError: connect ECONNREFUSED 127.0.0.1:27017] Failed to auto-configure a DataSource: 'spring.datasource.url' is not specified Failed to start mongod.service: Unit mongod.service not found db.collection is not a function when using MongoClient v3.0 MongoError: connect ECONNREFUSED 127.0.0.1:27017 MongoDB: How To Delete All Records Of A Collection in MongoDB Shell? How to resolve Nodejs: Error: ENOENT: no such file or directory How to create a DB for MongoDB container on start up?

Examples related to node.js

Hide Signs that Meteor.js was Used Querying date field in MongoDB with Mongoose SyntaxError: Cannot use import statement outside a module Server Discovery And Monitoring engine is deprecated How to fix ReferenceError: primordials is not defined in node UnhandledPromiseRejectionWarning: This error originated either by throwing inside of an async function without a catch block dyld: Library not loaded: /usr/local/opt/icu4c/lib/libicui18n.62.dylib error running php after installing node with brew on Mac internal/modules/cjs/loader.js:582 throw err DeprecationWarning: Buffer() is deprecated due to security and usability issues when I move my script to another server Please run `npm cache clean`

Examples related to mongoose

Querying date field in MongoDB with Mongoose Server Discovery And Monitoring engine is deprecated Avoid "current URL string parser is deprecated" warning by setting useNewUrlParser to true Mongodb: failed to connect to server on first connect Push items into mongo array via mongoose Mongoose: findOneAndUpdate doesn't return updated document mongoError: Topology was destroyed Difference between MongoDB and Mongoose How can you remove all documents from a collection with Mongoose? E11000 duplicate key error index in mongodb mongoose