Implementing SQL- Like Cascades in Mongoose
Tags: productivity, javascript, node, mongodb
Published: 2019-01-07 23:02:43

I was very recently working on a hobby project: a role based access control application where I needed to implement `cascade delete` in some of the models.
The user model for instance, had a `roles` attribute which was an array of the `ObjectId`'s of the role model:

```javascript
const mongoose = require("mongoose");

const UserSchema = new mongoose.Schema({
  username: {
    type: String,
    unique: true,
    required: true
  },
  password: {
    type: String,
    required: true
  },
  roles: [
    {
      type: mongoose.Schema.Types.ObjectId,
      required: true,
      ref: "Role"
    }
  ]
});

```

Roles are dynamically created and assigned to users. The problem I was facing though, is that when a Role is deleted, it's `ObjectId` references in the user model were not also deleted, therefore becoming [orphaned](https://database.guide/what-is-an-orphaned-record), hence breaking referential integrity.

To prevent this, I needed to implement `cascade delete`. In SQL, it's as simple as adding the `ON DELETE CASCADE` referential action when creating your `users_roles` table like so:

```sql
CREATE TABLE users_roles (
	user_id int unsigned not null, 
	role_id int unsigned not null, 
	PRIMARY KEY (user_id, role_id), 
	FOREIGN KEY (user_id) REFERENCES users (id) 
		ON DELETE CASCADE, 
	FOREIGN KEY(role_id) REFERENCES roles (id)
	        ON DELETE CASCADE
);
```

But I was using [mongodb](https://www.mongodb.com) and the [mongoose](https://mongoosejs.com) ODM. How was I supposed to go about this?  I was able to solve this by utilizing mongoose [middlewares](https://mongoosejs.com/docs/middleware.html). As per their documentation, mongoose middlewares or hooks are functions that are passed control during execution of asynchronous functions. The `post` middleware or hook in particular is exactly what I needed as it would allow me to perform some actions immediately after a database operation completes:

```javascript
const mongoose = require("mongoose");
const User = require("./user");

const RoleSchema = new mongoose.Schema({
  title: {
    type: String,
    required: true
  }
});

RoleSchema.post("remove", document => {
  const roleId = document._id;
  User.find({ roles: { $in: [roleId] } }).then(users => {
    Promise.all(
      users.map(user =>
        User.findOneAndUpdate(
          user._id,
          { $pull: { roles: roleId } },
          { new: true }
        )
      )
    );
  });
});

module.exports = mongoose.model("Role", RoleSchema);

```

Let me briefly describe the code above. Immediately after deleting a particular role, I find all users that have that role id in their array of roles using mongodb's `$in` operator (mongoose allows you to use native mongodb [operators](https://docs.mongodb.com/manual/reference/operator/) in your queries).

```javascript
....
User.find({ roles: { $in: [roleId] } })
``` 
Then for each user that satisfies that criteria, another operator, the `$pull` operator is used to "pull" or remove that specific role id from their array of roles.

```javascript
....
User.findOneAndUpdate(
  user._id,
  { $pull: { roles: roleId } },
  { new: true }
)
```

This is the approach I used to implement `cascade delete` in mongoose to ensure referential integrity. I would love to hear of the other approaches you might use to solve a similar problem in mongoose.

Happy coding, everyone!