Mongoose: deleteOne middleware for cascading delete not working Mongoose: deleteOne middleware for cascading delete not working mongoose mongoose

Mongoose: deleteOne middleware for cascading delete not working


It depends whether you call deleteOne on document or on model. The later just have no document to bind it to.

The former gives you the document as you expect:

const project = await ProjectModel.findOne();project.deleteOne();

The later gives you the Query. There is no _id in the query, but it has this.op for example, which in this middleware will be "deleteOne":

await ProjectModel.deleteOne();

The only way to get the document id in this case is to ensure it is provided in the query:

await ProjectModel.deleteOne({_id: "alex"});

Then you can get it in the middleware from the filter:

const projectId = this.getFilter()["_id"]

You can specify query: false in second parameter of the middleware to ensure the it is not invoked when you call deleteOne on model. So the best you can do:

ProjectSchema.pre("deleteOne", {document:true, query: false}, (next) => {    const projectId = this._id;    ....});ProjectSchema.pre("deleteOne", {document:false, query: true}, (next) => {    const projectId = this.getFilter()["_id"];    if (typeof projectId === "undefined") {        // no way to make cascade deletion since there is no _id        // in the delete query        // I would throw an exception, but it's up to you how to deal with it        // to ensure data integrity    }});

Please take a look at corresponding tests on v5.7.12: https://github.com/Automattic/mongoose/blob/5.7.12/test/model.middleware.test.js#L436


In the mongoose docs it says "Model.deleteOne() does not trigger pre('remove') or post('remove') hooks."

There is solution if you can refactor your delete operations with findByIdAndDelete, it triggers the findOneAndDelete middleware,

So we can add this middleware to Project Schema.

Project model:

const mongoose = require("mongoose");const ProjectChild = require("./projectChild");const ProjectSchema = new mongoose.Schema({  name: String});ProjectSchema.post("findOneAndDelete", async function(doc) {  console.log(doc);  if (doc) {    const deleteResult = await ProjectChild.deleteMany({      parentProject: doc._id    });    console.log("Child delete result: ", deleteResult);  }});module.exports = mongoose.model("Project", ProjectSchema);

ProjectChild model:

const mongoose = require("mongoose");const projectChildSchema = new mongoose.Schema({  name: String,  parentProject: {    type: mongoose.Schema.Types.ObjectId,    ref: "Project"  }});module.exports = mongoose.model("ProjectChild", projectChildSchema);

I created a project like this:

{    "_id": "5dea699cb10c442260245abf",    "name": "Project 1",    "__v": 0}

And created 2 project child for this project:

Child 1

{    "_id": "5dea69c7b10c442260245ac0",    "name": "Child 1 (project 1)",    "parentProject": "5dea699cb10c442260245abf",    "__v": 0}

Child 2

{    "_id": "5dea69e8b10c442260245ac1",    "name": "Child 2 (project 1)",    "parentProject": "5dea699cb10c442260245abf",    "__v": 0}

I created a sample route to delete a project by its id like this:

router.delete("/project/:id", async (req, res) => {  const result = await Project.findByIdAndDelete(req.params.id);  res.send(result);});

When I send a DELETE request to this route, we see the following info in the console:

console.log(doc);

{ _id: 5dea699cb10c442260245abf, name: 'Project 1', __v: 0 }

console.log("Child delete result: ", deleteResult);

Child delete result:  { n: 2, ok: 1, deletedCount: 2 }

So we could deleted the 2 children of the project, when we deleted the project.

As an alternative you can also use findOneAndRemove, it triggers findOneAndRemove post middleware.

So in the ProjectSchema we replace the post middleware like this:

ProjectSchema.post("findOneAndRemove", async function(doc) {  console.log(doc);  if (doc) {    const deleteResult = await ProjectChild.deleteMany({      parentProject: doc._id    });    console.log("Child delete result: ", deleteResult);  }});

When we use a findOneAndRemove operation, the result will be the same as the first alternative:

const result = await Project.findOneAndRemove({ _id: req.params.id });