325

Basically I have a mongodb collection called 'people' whose schema is as follows:

people: {
         name: String, 
         friends: [{firstName: String, lastName: String}]
        }

Now, I have a very basic express application that connects to the database and successfully creates 'people' with an empty friends array.

In a secondary place in the application, a form is in place to add friends. The form takes in firstName and lastName and then POSTs with the name field also for reference to the proper people object.

What I'm having a hard time doing is creating a new friend object and then "pushing" it into the friends array.

I know that when I do this via the mongo console I use the update function with $push as my second argument after the lookup criteria, but I can't seem to find the appropriate way to get mongoose to do this.

db.people.update({name: "John"}, {$push: {friends: {firstName: "Harry", lastName: "Potter"}}});
Xavier Guihot
  • 54,987
  • 21
  • 291
  • 190
Neurax
  • 3,657
  • 2
  • 13
  • 18
  • I see that I could potentially use collections.findOneAndUpdate(), but I'm not sure where I'd implement that? In my model? – Neurax Oct 10 '15 at 03:02

12 Answers12

525

Assuming, var friend = { firstName: 'Harry', lastName: 'Potter' };

There are two options you have:

Update the model in-memory, and save (plain javascript array.push):

person.friends.push(friend);
person.save(done);

or

PersonModel.update(
    { _id: person._id }, 
    { $push: { friends: friend } },
    done
);

I always try and go for the first option when possible, because it'll respect more of the benefits that mongoose gives you (hooks, validation, etc.).

However, if you are doing lots of concurrent writes, you will hit race conditions where you'll end up with nasty version errors to stop you from replacing the entire model each time and losing the previous friend you added. So only go to the latter when it's absolutely necessary.

Gregory
  • 73
  • 1
  • 6
Adrian Schneider
  • 7,239
  • 1
  • 19
  • 13
  • 2
    I thought the second option was actually the safer one in terms of the concurrent write protection? Did you accidentally say latter when you meant to say former? – Will Brickner Nov 15 '17 at 05:46
  • 85
    Yep, I just checked and (in Nov 2017), **it _IS_ safe** to use the mongoose `update` function, while **it _IS NOT_ safe** to find a document, modify it in memory, and then call the `.save()` method on the document. When I say that an operation is 'safe', it means that even if one, two, or 50 changes are being applied to a document, they will all be successfully applied and the behavior of the updates will be as you expect. Essentially in-memory manipulation is unsafe because you could be working on an outdated document so when you save, changes which occurred after the fetch step are lost. – Will Brickner Nov 15 '17 at 05:55
  • 3
    @Will Brickner a common workflow for that is to wrap your logic in a retry loop: (retryable contains fetch, modify, save). If you get back a VersionError (optimistic lock exception), you can re-try that operation a few times and fetch a new copy each time. I still prefer atomic updates when possible, though! – Adrian Schneider Nov 15 '17 at 17:15
  • 11
    @ZackOfAllTrades Confused me too, but I believe done is the callback function. `let done = function(err, result) { // this runs after the mongoose operation }` – user Aug 08 '19 at 22:17
  • how to get the id of friend object in callback? – Dee Nix Nov 16 '19 at 08:53
  • 1
    Don't you have to use `markUpdated` in this scenario? – Anthony Jul 26 '20 at 23:47
  • Regarding "However, if you are doing lots of concurrent writes": I'd say you need no "special" code or case to run into such race-conditions. Imagine a person clicking randomly on "suggested friends" - multiple requests in parallel all appending items to the same array. – meandre Feb 18 '21 at 14:49
  • Hello! I tried this but it wouldn't work if friends is a subdocument (instead of an array in this case). May I ask how I can modify this to work for subdocumnets? – Reine_Ran_ Feb 22 '21 at 11:00
  • To push multiple values you can { $push: { friends: { $each: [ friend1, friend2 ] } } } – Nick Sinai Mar 02 '21 at 15:00
  • 1
    To complement what was already said, ** the Mongoose documentation recommends to "use save() rather than updateOne() and updateMany() where possible." ** Keep in mind that there are 2 benefits of using the latter methods: (i) there's a risk of running save() on a doc that has changed since it was stored in memory using the find() method -- updateOne is atomic and thus has no such risk; and (ii) doesn't require loading the doc using find(). This means 1 fewer query to run. Performance may improve, especially if the docs being updated are large. https://masteringjs.io/tutorials/mongoose/update – Tiago Mar 26 '21 at 22:27
  • You could also use $addToSet instead of $push which will check if what you're pushing exists in which case it will do nothing if it does. – Andreas C Apr 08 '21 at 15:31
  • 1
    I don't know if this was obvious or if this answer is outdated, but to new users like me- update is deprecated now so please use .findOneAndUpdate instead of update and use .exec() at the end. This worked for me. That is .findOneAndUpdate({}.{}).exec() – Kashish Arora May 19 '21 at 13:34
  • @AdrianSchneider did you mean you mainly use the second code example? I see that you suggested to use the first( plain javascript ) so to have mongoose's benefits.. – Vincenzo May 30 '21 at 08:01
  • @KashishArora thank you, I came here to comment about this. [Here is the link for the documentation if anyone is curious](https://mongoosejs.com/docs/deprecations.html#update). – Alvaro Carvalho Sep 27 '21 at 13:31
  • 1
    Can't thank you enough for this answer, I've been struggling with a similar issue for about 2 hours – Ahmed Adel Jun 01 '22 at 06:53
  • The second one worked better for me – tomstan11 Nov 08 '22 at 15:53
  • but first option operation is much expensive in compare to second options. – elias-soykat May 11 '23 at 08:15
93

The $push operator appends a specified value to an array.

{ $push: { <field1>: <value1>, ... } }

$push adds the array field with the value as its element.

Above answer fulfils all the requirements, but I got it working by doing the following

var objFriends = { fname:"fname",lname:"lname",surname:"surname" };
People.findOneAndUpdate(
   { _id: req.body.id }, 
   { $push: { friends: objFriends  } },
  function (error, success) {
        if (error) {
            console.log(error);
        } else {
            console.log(success);
        }
    });
)
Vivek
  • 61
  • 7
Parth Raval
  • 4,097
  • 3
  • 23
  • 36
  • 2
    This is the one that I have found to work, so I think this is the most up to date as of 2019. The one that is best answer I think is lacking in some way and maybe incomplete/ out of date. – Cheesus Toast Mar 09 '19 at 07:40
  • 1
    May I just point out that you may have a slight error in the code. I got it to work because I put the correct variable in for my project. Where you have Friend.findOneAndUpdate() I think it should be People.findOneAndUpdate(). That would be more in keeping with the original question. I could edit it for you but I would rather you just double check it in case I am wrong (I am a little new to node/ express). – Cheesus Toast Mar 09 '19 at 09:54
  • @CheesusToast I think, if i am wrong, you can change the answer. i have putted this answer which was working fine for me. but if you find this answer wrong, You can change this answer & i will be glad if you correct me sir :-). – Parth Raval Mar 18 '19 at 05:22
  • 2
    OK, no problem, it was only a small edit anyway. It is kind of nit-picky of me because the viewers (like me) would have worked out where the array was being pushed to anyway. I still think this should be best answer :) – Cheesus Toast Mar 19 '19 at 00:57
  • 2
    Just wanted to let others know that this works perfectly, it's 2020 and still works perfectly fine. I just used findById instead of findByOne though, anyways thanks for the help dude. – Mob_Abominator Jul 23 '20 at 04:18
38

Another way to push items into array using Mongoose is- $addToSet, if you want only unique items to be pushed into array. $push operator simply adds the object to array whether or not the object is already present, while $addToSet does that only if the object is not present in the array so as not to incorporate duplicacy.

PersonModel.update(
  { _id: person._id }, 
  { $addToSet: { friends: friend } }
);

This will look for the object you are adding to array. If found, does nothing. If not, adds it to the array.

References:

Avani Khabiya
  • 1,207
  • 2
  • 15
  • 34
24

Use $push to update document and insert new value inside an array.

find:

db.getCollection('noti').find({})

result for find:

{
    "_id" : ObjectId("5bc061f05a4c0511a9252e88"),
    "count" : 1.0,
    "color" : "green",
    "icon" : "circle",
    "graph" : [ 
        {
            "date" : ISODate("2018-10-24T08:55:13.331Z"),
            "count" : 2.0
        }
    ],
    "name" : "online visitor",
    "read" : false,
    "date" : ISODate("2018-10-12T08:57:20.853Z"),
    "__v" : 0.0
}

update:

db.getCollection('noti').findOneAndUpdate(
   { _id: ObjectId("5bc061f05a4c0511a9252e88") }, 
   { $push: { 
             graph: {
               "date" : ISODate("2018-10-24T08:55:13.331Z"),
               "count" : 3.0
               }  
           } 
   })

result for update:

{
    "_id" : ObjectId("5bc061f05a4c0511a9252e88"),
    "count" : 1.0,
    "color" : "green",
    "icon" : "circle",
    "graph" : [ 
        {
            "date" : ISODate("2018-10-24T08:55:13.331Z"),
            "count" : 2.0
        }, 
        {
            "date" : ISODate("2018-10-24T08:55:13.331Z"),
            "count" : 3.0
        }
    ],
    "name" : "online visitor",
    "read" : false,
    "date" : ISODate("2018-10-12T08:57:20.853Z"),
    "__v" : 0.0
}
hemnath mouli
  • 2,617
  • 2
  • 17
  • 35
KARTHIKEYAN.A
  • 18,210
  • 6
  • 124
  • 133
9

First I tried this code

const peopleSchema = new mongoose.Schema({
  name: String,
  friends: [
    {
      firstName: String,
      lastName: String,
    },
  ],
});
const People = mongoose.model("person", peopleSchema);
const first = new Note({
  name: "Yash Salvi",
  notes: [
    {
      firstName: "Johnny",
      lastName: "Johnson",
    },
  ],
});
first.save();
const friendNew = {
  firstName: "Alice",
  lastName: "Parker",
};
People.findOneAndUpdate(
  { name: "Yash Salvi" },
  { $push: { friends: friendNew } },
  function (error, success) {
    if (error) {
      console.log(error);
    } else {
      console.log(success);
    }
  }
);

But I noticed that only first friend (i.e. Johhny Johnson) gets saved and the objective to push array element in existing array of "friends" doesn't seem to work as when I run the code , in database in only shows "First friend" and "friends" array has only one element ! So the simple solution is written below

const peopleSchema = new mongoose.Schema({
  name: String,
  friends: [
    {
      firstName: String,
      lastName: String,
    },
  ],
});
const People = mongoose.model("person", peopleSchema);
const first = new Note({
  name: "Yash Salvi",
  notes: [
    {
      firstName: "Johnny",
      lastName: "Johnson",
    },
  ],
});
first.save();
const friendNew = {
  firstName: "Alice",
  lastName: "Parker",
};
People.findOneAndUpdate(
  { name: "Yash Salvi" },
  { $push: { friends: friendNew } },
  { upsert: true }
);

Adding "{ upsert: true }" solved problem in my case and once code is saved and I run it , I see that "friends" array now has 2 elements ! The upsert = true option creates the object if it doesn't exist. default is set to false.

if it doesn't work use below snippet

People.findOneAndUpdate(
  { name: "Yash Salvi" },
  { $push: { friends: friendNew } },
).exec();
Raxy
  • 335
  • 1
  • 3
  • 9
6

An easy way to do that is to use the following:

var John = people.findOne({name: "John"});
John.friends.push({firstName: "Harry", lastName: "Potter"});
John.save();
Felipe Toledo
  • 599
  • 1
  • 10
  • 16
  • 1
    Prone to race-conditions (if you instantiate several Johns in parallel, you'll lose one of the pushed values). – meandre Feb 18 '21 at 14:44
4

In my case, I did this

  const eventId = event.id;
  User.findByIdAndUpdate(id, { $push: { createdEvents: eventId } }).exec();
Prathamesh More
  • 1,470
  • 2
  • 18
  • 32
3

Push to nested field - use a dot notation

For anyone wondering how to push to a nested field when you have for example this Schema.

const UserModel = new mongoose.schema({
  friends: {
    bestFriends: [{ firstName: String, lastName: String }],
    otherFriends: [{ firstName: String, lastName: String }]
  }
});

You just use a dot notation, like this:

const updatedUser = await UserModel.update({_id: args._id}, {
  $push: {
    "friends.bestFriends": {firstName: "Ima", lastName: "Weiner"}
  }
});
Jan Karnik
  • 175
  • 1
  • 7
1

This is how you could push an item - official docs

const schema = Schema({ nums: [Number] });
const Model = mongoose.model('Test', schema);

const doc = await Model.create({ nums: [3, 4] });
doc.nums.push(5); // Add 5 to the end of the array
await doc.save();

// You can also pass an object with `$each` as the
// first parameter to use MongoDB's `$position`
doc.nums.push({
  $each: [1, 2],
  $position: 0
});
doc.nums;
MD SHAYON
  • 7,001
  • 45
  • 38
0

// This is the my solution for this question.

// I want to add new object in worKingHours(array of objects) -->

workingHours: [
  {
    workingDate: Date,
    entryTime: Date,
    exitTime: Date,
  },
],

// employeeRoutes.js

const express = require("express");
const router = express.Router();
const EmployeeController = require("../controllers/employeeController");



router
  .route("/:id")
  .put(EmployeeController.updateWorkingDay)

// employeeModel.js

const mongoose = require("mongoose");
const validator = require("validator");

const employeeSchema = new mongoose.Schema(
  {
    name: {
      type: String,
      required: [true, "Please enter your name"],
    },
    address: {
      type: String,
      required: [true, "Please enter your name"],
    },
    email: {
      type: String,
      unique: true,
      lowercase: true,
      required: [true, "Please enter your name"],
      validate: [validator.isEmail, "Please provide a valid email"],
    },
    phone: {
      type: String,
      required: [true, "Please enter your name"],
    },
    joiningDate: {
      type: Date,
      required: [true, "Please Enter your joining date"],
    },
    workingHours: [
      {
        workingDate: Date,
        entryTime: Date,
        exitTime: Date,
      },
    ],
  },
  {
    toJSON: { virtuals: true },
    toObject: { virtuals: true },
  }
);

const Employee = mongoose.model("Employee", employeeSchema);

module.exports = Employee;

// employeeContoller.js

/////////////////////////// SOLUTION IS BELOW ///////////////////////////////

// This is for adding another day, entry and exit time

exports.updateWorkingDay = async (req, res) => {
  const doc = await Employee.findByIdAndUpdate(req.params.id, {
    $push: {
      workingHours: req.body,
    },
  });
  res.status(200).json({
    status: "true",
    data: { doc },
  });
};

https://www.youtube.com/watch?v=gtUPPO8Re98

0
let doc = await this.peopleModel.findById(_id);
    doc.friends.push()
    doc.save();

Work nice! because I want to return the changes to respond immediately in the backend

-3

I ran into this issue as well. My fix was to create a child schema. See below for an example for your models.

---- Person model

const mongoose = require('mongoose');
const SingleFriend = require('./SingleFriend');
const Schema   = mongoose.Schema;

const productSchema = new Schema({
  friends    : [SingleFriend.schema]
});

module.exports = mongoose.model('Person', personSchema);

***Important: SingleFriend.schema -> make sure to use lowercase for schema

--- Child schema

const mongoose = require('mongoose');
const Schema   = mongoose.Schema;

const SingleFriendSchema = new Schema({
  Name: String
});

module.exports = mongoose.model('SingleFriend', SingleFriendSchema);
  • Possibly the reason for the downvote is because this does not seem necessary. Parth Raval's answer is quite sufficient. – Cheesus Toast Mar 09 '19 at 07:44
  • Downvotes are because this is a poor hacky solution opposed to the clean, native solution of other answers – TOPKAT Sep 24 '20 at 16:44