104

I'm looking for a way to refactor part of my code to be shorter and simpler, but I don't know Mongoose very well and I'm not sure how to proceed.

I am trying to check a collection for the existence of a document and, if it doesn't exist, create it. If it does exist, I need to update it. In either case I need to access the document's contents afterward.

What I've managed to do so far is query the collection for a specific document and, if it's not found, create a new document. If it is found, I update it (currently using dates as dummy data for this). From there I can access either the found document from my initial find operation or the newly saved document and this works, but there must be a better way to accomplish what I'm after.

Here's my working code, sans distracting extras.

var query = Model.find({
    /* query */
}).lean().limit(1);

// Find the document
query.exec(function(error, result) {
    if (error) { throw error; }
    // If the document doesn't exist
    if (!result.length) {
        // Create a new one
        var model = new Model(); //use the defaults in the schema
        model.save(function(error) {
            if (error) { throw error; }
            // do something with the document here
        });
    }
    // If the document does exist
    else {
        // Update it
        var query = { /* query */ },
            update = {},
            options = {};

        Model.update(query, update, options, function(error) {
            if (error) { throw error; }
            // do the same something with the document here
            // in this case, using result[0] from the topmost query
        });
    }
});

I've looked into findOneAndUpdate and other related methods but I'm not sure if they fit my use case or if I understand how to use them correctly. Can anyone point me in the right direction?

(Probably) Related questions:


Edit

I didn't come across the question pointed out to me in my searching, but after reviewing the answers there I've come up with this. It's certainly prettier, in my opinion, and it works, so unless I'm doing something horribly wrong I think my question can probably be closed.

I would appreciate any additional input on my solution.

// Setup stuff
var query = { /* query */ },
    update = { expire: new Date() },
    options = { upsert: true };

// Find the document
Model.findOneAndUpdate(query, update, options, function(error, result) {
    if (!error) {
        // If the document doesn't exist
        if (!result) {
            // Create it
            result = new Model();
        }
        // Save the document
        result.save(function(error) {
            if (!error) {
                // Do something with the document
            } else {
                throw error;
            }
        });
    }
});
Community
  • 1
  • 1
Connor
  • 1,815
  • 3
  • 17
  • 25
  • 1
    Que:Mongoose.js: how to implement create or update? Ans: http://stackoverflow.com/questions/7267102/how-do-i-update-upsert-a-document-in-mongoose – Alok Deshwal Oct 23 '15 at 15:34
  • I feel pretty foolish now, honestly. I didn't find that question when I was searching, but in retrospect the answer seems pretty simple to grasp. Thank you for the help! – Connor Oct 23 '15 at 16:51

5 Answers5

177

You are looking for the new option parameter. The new option returns the newly created document(if a new document is created). Use it like this:

var query = {},
    update = { expire: new Date() },
    options = { upsert: true, new: true, setDefaultsOnInsert: true };

// Find the document
Model.findOneAndUpdate(query, update, options, function(error, result) {
    if (error) return;

    // do something with the document
});

Since upsert creates a document if not finds a document, you don't need to create another one manually.

  • 2
    This appears to work, except that the newly created document is not populated with the default values defined in my schema. Very confusing. Any reason why that might be, do you know? – Connor Nov 01 '15 at 16:09
  • 2
    @Connor Yea, it is a default feature in mongoose, although there is an option to fix this. Check my updated answer. –  Nov 01 '15 at 20:39
  • 1
    Kind of a strange little quirk, but I s'pose the developers had their reasons. This is a pretty fantastic solution to my problem, so thank you for your help! – Connor Nov 04 '15 at 17:59
  • 1
    in that case how do you identify which status code send? 200= updated . 201 created – jhonny lopez Jan 18 '19 at 16:53
  • What if the object I'm trying to update has a list of ids and I want to add a new id into it? will this replace or add to the list? – shinzou Dec 16 '20 at 22:43
  • Newly created document only populated with default fields define in my Schema, its not populate other fields? – jyoti Mar 03 '22 at 05:57
23

Since you wish to refactor parts of your code to be shorter and simpler,

  1. Use async / await
  2. Use .findOneAndUpdate() as suggested in this answer

let query = { /* query */ };
let update = {expire: new Date()};
let options = {upsert: true, new: true, setDefaultsOnInsert: true};
let model = await Model.findOneAndUpdate(query, update, options);
Jossef Harush Kadouri
  • 32,361
  • 10
  • 130
  • 129
0
///This is simple example explaining findByIDAndUpdate from my code added with try catch block to catch errors
try{
const options = {
            upsert: true,
            new: true,
            setDefaultsOnInsert: true
        };
        const query = {
            $set: {
                description: req.body.description,
                title: req.body.title
            }
        };
        const survey = await Survey.findByIdAndUpdate(
            req.params.id,
            query,
            options
        ).populate("questions");
}catch(e){
console.log(e)
}
0

Here is an example I am using. I have to return custom responses for UI updates etc. This can be even shorter. User is

const UserScheme = mongoose.Schema({
    _id: String,
    name: String,
    city: String,
    address: String,
},{timestamps: true});

const User = mongoose.model('Users', UserScheme);


async function userUpdateAdd(data){
    var resp = '{"status": "error"}';
    if(data){
    var resp = await User.updateOne({ _id: data._id }, data).then(function(err, res){
        console.log("database.userUpdateAdd -> Update data saved in database!");
        if(err){
            var errMessage = err.matchedCount == 0 ? "User Record does not exist, will create new..." : "Record not updated";
            // If no match, create new
            if(err.matchedCount == 0){
                const create_user = new User(data);
                resp = create_user.save().then(function(){
                    console.log("database.userUpdateAdd -> Data saved to database!");
                    return '{"status":"success", "message": "New User added successfully"}';
                });
                return resp;
            }

            // Exists, return success update message
            if(err.matchedCount == 1){
                return '{"status": "success", "message" : "Update saved successfully"}';
            } else {
                return '{"status": "error", "code": "' + err.modifiedCount + '", "message": "' + errMessage + '"}';
            }
        }
        })
        .catch((error) => {
            //When there are errors We handle them here
            console.log("database.userUpdateAdd -> Error, data not saved! Server error");
            return '{"status": "error", "code": "400", "message": "Server error!"}';
        });
    }
    return resp;
}
Maka
  • 371
  • 3
  • 14
-1

Here's an example:

const mongoose = require('mongoose');
mongoose.connect('mongodb://localhost/rsvp', {useNewUrlParser: true, useUnifiedTopology: true});

const db = mongoose.connection;

db.on('error', () => {
  console.log('mongoose connection error');
});

db.once('open', () => {
  console.log('mongoose connected successfully');
});

const rsvpSchema = mongoose.Schema({
  firstName: String,
  lastName: String,
  email: String,
  guests: Number
});

const Rsvp = mongoose.model('Rsvp', rsvpSchema);


// This is the part you will need... In this example, if first and last name match, update email and guest number. Otherwise, create a new document. The key is to learn to put "upsert" as the "options" for the argument.
const findRsvpAndUpdate = (result, callback) => {

  Rsvp.findOneAndUpdate({firstName: result.firstName, lastName: result.lastName}, result, { upsert: true }, (err, results) => {
    if (err) {
      callback(err);
    } else {
      callback(null, results);
    }
  })
};


// From your server index.js file, call this...
app.post('/rsvps', (req, res) => {
  findRsvpAndUpdate(req.body, (error, result) => {
    if (error) {
      res.status(500).send(error);
    } else {
      res.status(200).send(result);
    }
  })
});
hongkongbboy
  • 300
  • 1
  • 5
  • 14