Search code examples
mongodbmongoose

Push an object into a nested array in MongoDB


I've got a head-scratcher here that I'd like to share with you all.

So here's the model:

    _id: ObjectId()
    name: String,
    columns: [
      {
        name: String,
        _id: ObjectId()
        tasks: [
          {
            title: String,
            description: String,
            status: String,
            _id: ObjectId()
            subtasks: [
              {
                title: String,
                isCompleted: Boolean,
              },
            ],
          },
        ],
      },
    ],
  });

and the query:

exports.createSubtask = (req, res) => {
  if (!req.body) {
    res.status(400).send({ message: "Task name can not be empty!" });
    return;
  }

  const board = req.params.board;
  const column = req.params.column;
  const task = req.params.task;

  Board.findOneAndUpdate(
    {
      _id: board,
      "columns._id": column,
      "columns.tasks._id": task,
    },
    {
      $push: {
        "columns.$.tasks.$.subtasks": req.body,
      },
    }
  )
    .then((data) => {
      if (!data) {
        res.status(404).send({
          message: `Cannot update Task with id=${task}. Maybe task was not found!`,
        });
      } else res.send({ message: "Task was updated successfully." });
    })
    .catch((err) => {
      res.status(500).send({
        message: "Error updating Task with id=" + task,
      });
    });
};

I'm trying to push an object into the subtasks array with $push, but Postman is throwing an error.

Any ideas as to what I'm doing wrong? Appreciate the help.

  • Golden Ratio

However, I was able to successfully push an object into the tasks array with the following query:

exports.createTask = (req, res) => {
  if (!req.body) {
    res.status(400).send({ message: "Task name can not be empty!" });
    return;
  }

  const board = req.params.board;
  const column = req.params.column;

  Board.findOneAndUpdate(
    {
      _id: board,
      "columns._id": column,
    },
    {
      $push: {
        "columns.$.tasks": req.body,
      },
    }
  )
    .then((data) => {
      if (!data) {
        res.status(404).send({
          message: `Cannot update Column with id=${column}. Maybe column was not found!`,
        });
      } else res.send({ message: "Column was updated successfully." });
    })
    .catch((err) => {
      res.status(500).send({
        message: "Error updating Column with id=" + column,
      });
    });
};

Solution

  • It is not possible to use multiple positional $ for the nested array as mention in docs:

    The positional $ operator cannot be used for queries which traverse more than one array, such as queries that traverse arrays nested within other arrays, because the replacement for the $ placeholder is a single value

    You should work with the positional filtered operator $[<identifier>].

    Board.findOneAndUpdate(
        {
          _id: board,
          "columns._id": column,
          "columns.tasks._id": task,
        },
        {
          $push: {
            "columns.$.tasks.$[task].subtasks": req.body,
          },
        },
        {
          arrayFilters: [
            { "task._id": task }
          ]
        }
      )
    .then(...);
    

    Note: Ensure that the passed in task is ObjectId type.