π οΈ Soft Delete Plugin for Mongoose - Simplify Data Management! π

π₯ Introduction
Managing soft deletes in MongoDB using Mongoose can be tedious and repetitive. Every time you query your database, you need to remember to filter out isDeleted: false
, which can lead to unnecessary complexity and potential mistakes. Manually handling this logic across multiple queries increases the risk of inconsistencies and inefficiencies. To streamline this process, I created a Mongoose Soft Delete Plugin that automatically manages soft deletes for you. This plugin ensures that queries remain clean, concise, and efficient, allowing you to focus on building features instead of handling soft delete logic manually. π
β¨ What This Plugin Does
β
Automatically adds isDeleted
& deletedAt
fields π·οΈ
β
Filters out soft-deleted documents in all queries π
β
Custom Methods: .softDeleteOne()
& .softDeleteMany()
π οΈ
β
Works with find
, update
, and aggregate
queries seamlessly β‘
ποΈ Implementation
Hereβs how the plugin is implemented:
import { nowUTCDate } from "@/utils/helpers/date"; // new UTC Date
import mongoose, { CallbackWithoutResultAndOptionalError } from "mongoose";
const softDeletePlugin = (schema: mongoose.Schema) => {
// Add soft delete fields to schema
schema.add({
isDeleted: { type: Boolean, required: true, default: false },
deletedAt: { type: Date, default: null },
});
// Middleware to exclude soft-deleted documents from queries
const queryTypes = [
"count", "find", "findOne", "findOneAndDelete", "findOneAndRemove",
"findOneAndUpdate", "update", "updateOne", "updateMany",
];
const excludeDeletedInQueries = async function (
this: mongoose.Query<any, any>,
next: CallbackWithoutResultAndOptionalError
) {
this.where({ isDeleted: false });
next();
};
const excludeDeletedInAggregates = async function (
this: mongoose.Aggregate<any>,
next: CallbackWithoutResultAndOptionalError
) {
this.pipeline().unshift({ $match: { isDeleted: false } });
next();
};
// Custom soft delete methods
schema.statics.softDeleteOne = async function (filter, otherUpdates) {
await this.updateOne(filter, {
$set: { isDeleted: true, deletedAt: nowUTCDate(), ...otherUpdates },
});
};
// Custom soft delete methods
schema.statics.softDeleteMany = async function (filter, otherUpdates) {
await this.updateMany(filter, {
$set: { isDeleted: true, deletedAt: nowUTCDate(), ...otherUpdates },
});
};
// Apply middleware
queryTypes.forEach((type) => {
schema.pre(type as any, excludeDeletedInQueries);
});
// Apply middleware
schema.pre("aggregate", excludeDeletedInAggregates);
};
export { softDeletePlugin };
π©βπ» Example: User Schema with Soft Delete
Letβs see how we can use this plugin in a User Schema:
import mongoose from "mongoose";
import { SoftDeleteModel, softDeletePlugin } from "../../softDelete";
export interface IUser {
name: string;
email: string;
age: number;
}
export interface IUserDocument extends IUser, mongoose.Document {
createdAt: Date;
updatedAt: Date;
}
const UserSchema = new mongoose.Schema<IUserDocument>(
{
name: {
type: String,
required: true,
},
email: {
type: String,
required: true,
unique: true,
},
age: {
type: Number,
required: false,
},
},
{
timestamps: true,
}
);
// Apply soft delete plugin
UserSchema.plugin(softDeletePlugin);
export const User = mongoose.model<IUserDocument, SoftDeleteModel<IUserDocument>>("User", UserSchema);
const userIds = ["ids",..]
await User.softDeleteMany(
{ _id: { $in: userIds } },
{ age: { $lt:18 }, isBnboarded:false } //other fields
);
π Querying Without Explicit Filtering
Before: (Without plugin)
const users = await User.find({ isDeleted: false });
After: (With plugin)
const users = await User.find(); // No need to manually filter π
β‘ Updating Without Fetching Soft-Deleted Docs
const updatedUser = await User.findOneAndUpdate(
{ _id: userId },
{ age: 19 },
{ new: true }
);
π Aggregation Query (Without Manually Filtering isDeleted
)
Before: (Without plugin)
const result = await User.aggregate([
{ $match: { isDeleted: false } },
{ $group: { _id: "$age", count: { $sum: 1 } } }
]);
After: (With plugin)
const result = await User.aggregate([
{ $group: { _id: "$age", count: { $sum: 1 } } }
]);
π― Making Soft Deletes Effortless! π
This plugin ensures that soft-deleted documents are seamlessly handled without extra work! No more manual filtering in queries, updates, or aggregations. Just plug it in and focus on your business logic. π―
Subscribe to my newsletter
Read articles from Arjun Dangi directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
