Soft Delete 🗑 – Node.js, MongoDB

In this article, we will teach you, how you can implement soft delete in your Node.js app with MongoDB as database.

We also created a tutorial on how to do soft delete in Laravel, check our tutorial here.

Soft delete 🗑 is useful when you do not want to actually delete the record from database, but mark the data as deleted.

You might have seen “This message has been deleted” on WhatsApp đŸ’Ŧ when you delete a message. That was soft delete. Because if you actually delete the message from database, then you would have no reference if there was a message at that time.

It is also helpful in cases where you want to allow the user to delete some data, but wants the administrator 👨đŸģ‍đŸ’ŧ to see the data. So when user deletes the data, it will not actually delete the data from database. It will simply hide it from user, but administrator can still see it. Only administrator can actually delete the data.

We will use “users” as entity for the sake of this tutorial.

In this article, you will learn:

  1. Insert users in MongoDB
  2. Fetch non-trashed users
  3. Soft delete a user
  4. Fetch trashed users
  5. Restore a user
  6. Permanently delete a user

1. Insert users in MongoDB ➕

Following API will insert a new document in MongoDB “users” collection using Node.js:

// API to insert data
app.post("/insert", async function (request, result) {
    // Get input fields
    const name = request.fields.name || "";

    // Insert data in database
    await db.collection("users")
        .insertOne({
            name: name,
            deletedAt: null // This will save the time when user was deleted
        });

    // Return response to the client
    result.json({
        status: "success",
        message: "User has been inserted."
    });
});

It will get user name from client app and insert a new document in “users” collection. Note that it creates another field “deletedAt” and set it’s value to null. Having NULL value means that the user is not deleted.

2. Fetch non-trashed users

Then we are going to fetch all the users that are not deleted.

// API to fetch non-trashed users
app.post("/fetch", async function (request, result) {
    const users = await db.collection("users")
        .find({
            deletedAt: null
        }).toArray();

    result.json({
        status: "success",
        message: "Users has been fetched.",
        users: users
    });
});

Here we are using filter to fetch all the records where deletedAt field is null.

3. Soft delete a user ␥

Now we will create an API that will soft delete a user.

// API to move the user to trash can
app.post("/delete", async function (request, result) {
    // Get user ID
    const _id = request.fields._id || "";

    // Don't delete the document,
    // just update the deletedAt field
    await db.collection("users")
        .updateOne({
            _id: ObjectId.createFromHexString(_id)
        }, {
            $set: {
                deletedAt: new Date()
            }
        });

    result.json({
        status: "success",
        message: "User has been moved to trash can."
    });
});

Here, we are first getting the user ID from client app. Then we are setting the deletedAt field to the current date.

If you run the /fetch API again, you will not see that user’s data in the response. Even though the record exists in the database, it will not be returned.

4. Fetch trashed users

Now that we have trashed the user, we need to find a way to see all those trashed users. So we will create an API that will return all the users that has been soft-deleted.

// API to see all trashed users
app.post("/trashed", async function (request, result) {
    const users = await db.collection("users")
        .find({
            deletedAt: {
                $ne: null
            }
        }).toArray();

    result.json({
        status: "success",
        message: "Trashed users has been fetched.",
        users: users
    });
});

At this point, you can give administrator 2 options, either:

  1. Restore the user
  2. Delete the user permanently

5. Restore a user ↩ī¸

Restoring the user will simply set the deletedAt field of user back to null, like it was when user was created.

// API to restore a user
app.post("/restore", async function (request, result) {
    const _id = request.fields._id || "";

    await db.collection("users")
        .updateOne({
            _id: ObjectId.createFromHexString(_id)
        }, {
            $set: {
                deletedAt: null
            }
        });

    result.json({
        status: "success",
        message: "User has been restored."
    });
});

This will also receive a user ID that needs to be restored. If you call the /fetch API again, you will see that user in the response.

6. Permanently delete a user 🗑

Permanently deleting a user will actually deletes the user from database. There will no way to restore it once done (unless you have a backup 🎒 of your database).

// API to actually delete the user from database
app.post("/delete-permanently", async function (request, result) {
    const _id = request.fields._id || "";

    await db.collection("users")
        .deleteOne({
            $and: [{
                _id: ObjectId.createFromHexString(_id)
            }, {
                deletedAt: {
                    $ne: null
                }
            }]
        });

    result.json({
        status: "success",
        message: "User has been permanently deleted."
    });
});

Usually, this access is given to super admin only.

Bonus ⭐ī¸

One more thing you can do, is if you have saved the user ID in other collections as well, then you can save the data in the dependent collections before removing them.

For example, if you have an “orders” collection, where you are saving user’s orders in the following format:

{
    "_id": ObjectId("1234567890"),
    "userId": ObjectId("0987654321")
}

If you permanently delete the user, then this order’s data will be lost as well. So best practice is to:

  1. Fetch the user.
  2. Put in orders collection.
  3. Delete the user.

Following is the code for that:

const user = await db.collection("users")
    .findOne({
        _id: ObjectId.createFromHexString(_id)
    });

if (user == null) {
    result.json({
        status: "error",
        message: "User not found."
    });
    return;
}

await db.collection("orders")
    .updateMany({
        userId: user._id
    }, {
        $set: {
            user: {
                _id: user._id,
                name: user.name
            }
        }
    });

Now, when you delete the user, your “orders” collection will save the user data for historical use.

{
    "_id": ObjectId("1234567890"),
    "userId": ObjectId("0987654321"),
    "user": {
        "_id": ObjectId("0987654321"),
        "name": "Adnan"
    }
}

Conclusion 📝

So that’s it. That’s how you can create a soft delete feature in your Node.js application with MongoDB as backend. It is very helpful in almost every project and I would recommend everyone must apply it. Whenever user delete something, soft delete it so if user done that by mistake, he will be able to recover it.

2 ways to loop through a number in React

In this article, we will discuss 2 ways how you can loop through a number in React.

While developing frontend of an application đŸ–Ĩ, you might encounter a situation where you have to display a list of data. For example, a list of users etc.

Most common way to loop through an array in React is using Javascript built-in map function.

But if you have a number that you have to loop through. For example, run a loop till 10 etc.

In this case, we have 2 options.

1st way: Using function ☑ī¸

One way to loop through a number in React is to create a function that will:

  • Create an array.
  • Using basic for loop and push the HTML code in array.
  • Return the array.

Here is the code on how to do that. 👨đŸģ‍đŸ’ģ

function App() {

    const [number, setNumber] = React.useState(10);

    function renderLoop() {
        const elements = [];

        for (let a = 0; a < number; a++) {
            elements.push(
                <p key={ `1st-way-${ a }` }>{ a }</p>
            );
        }

        return elements;
    }

    return (
        <>
            { renderLoop() }
        </>
    );
}

Following will be the output of above code:

0
1
2
3
4
5
6
7
8
9

2nd way: Using map ✅

Javascript’s map function can also be used to iterate over an array and display a list without having to create a function.

function App() {

    const [number, setNumber] = React.useState(10);

    return (
        <>
            { Array.from({ length: number }, function (_, index) { return index })
                .map(function (n) {
                    return (
                        <p key={ `2nd-way-${ n }` }>{ n }</p>
                    )
            }) }
        </>
    );
}

Explanation đŸ•ĩđŸģ‍♂ī¸

  • The first argument of Array.from is an object that will create an array of length 10 such that each element has an index starting from 0.
  • The second argument is a mapping function.
    • First parameter is “_” which means the value.
    • Second parameter is “index”.
    • It is returning the index without any modification, so the original number will be returned.
    • It will create an array from 0 to 9.
  • Using the map function, you can perform some action on that array created from number. In this case, we are displaying each number n in a paragraph. 📄

So that’s how you can loop through a number in React. If you face any problem in following this, kindly do let me know. đŸ’Ŧ

Get updated value from Child to Parent – React

In this tutorial, we will learn how you can get updated value from child component to parent component in React.

Let’s say you have a parent child component in your React app. You are sending data from parent to child and your child component is updating that variable’s value. Now when you get the value in parent component, you still get the original value, not the updated one.

First, you need to create a reference and pass it to the child component.

function Parent() {

    // Create a reference
    const childRef = React.useRef();

    // Create array of users
    const [users, setUsers] = React.useState([
        { id: 1, name: "Adnan" },
        { id: 2, name: "Afzal" }
    ]);

    // Function to get updated users
    function getUsers() {

        // Get users with reference
        let tempUsers = [];

        if (childRef.current) {
            tempUsers = childRef.current.getUsers();
        }

        console.log({
            "users": users,
            "tempUsers": tempUsers
        });
    }

    // Set child component and pass users array to it
    return (
        <>
            <Child users={ users }
                ref={ childRef } />

            {/* Button to get users */}

            <button type="button" onClick={ function () {
                getUsers();
            } }>Get users</button>
        </>
    );
}
  • childRef: First, we are creating a reference.
  • users: Then we are creating a state variable.
  • getUsers(): A function is created that will get the state value using reference.

We are passing the users array and reference to the child component.

A button is also created which when clicked, will call the function to get the state variable.

Then in your child component, you need to create it using forwardRef.

// Create child component that gets properties
// Export child using forward reference
const Child = React.forwardRef(function (props, ref) {

    // Get users from parent component
    const [users, setUsers] = React.useState(props.users || []);

    // Use imperative handler to return updated value
    React.useImperativeHandle(ref, function () {
        return {
            getUsers() {
                return users;
            }
        };
    });

    // Function to set user name to "Khalid" where user ID is 1
    function updateUser(id, name) {
        const tempUsers = JSON.parse(JSON.stringify(users));
        for (let a = 0; a < tempUsers.length; a++) {
            if (tempUsers[a].id == id) {
                tempUsers[a].name = name;
                break;
            }
        }
        setUsers(tempUsers);
    }

    // Create button to update user
    return (
        <>
            <button type="button" onClick={ function () {
                updateUser(1, "Khalid");
            } }>Update user</button>
        </>
    );
});

forwardRef: It is used to pass ref from parent component to child component.

Then creating a state variable and initialize it with props value received from parent component.

We are using imperative handler to return the updated value.

A button is created in child component that updates the value using it’s ID.

JSON.parse(JSON.stringify(users)): We are first converting the users array to JSON, then converting back to Javascript array. This ensures a deep copy.

That’s how you can get updated value from child component to parent component in React.