44-Nodejs Course 2023: Database Recycle Bin
Tags: node, typescript, mongodb, fastify
Published: 2022-11-06 21:03:50

---
title: 44-Nodejs Course 2023: Database Recycle Bin
published: true
description: 
series: Nodejs Course 2023
tags: nodejs, typescript, mongodb, fastify
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gcbn083id5inrhxsjr7o.png
# Use a ratio of 100:42 for best results.
---

We've proceeded with a nice progress in the saving process, but we still have a problem, what if we want to delete a document from the database but we actually don't want to delete it?

Well, we can use the `recycle bin` concept, we can just move the document to the recycle bin and then we can restore it.

## Recycle Bin

Here is how it works, when we call the `delete` method, we'll actually not deleting it, but we'll move it to another collection which will be called `collectionNameTrash` where `collectionName` is the name of the collection we want to delete the document from.

For example if we want to delete a document from the `users` collection, we'll move it to the `usersTrash` collection.

Now let's try it in the `destroy` method first.

```ts
// src/core/database/model/model.ts


  /**
   * Delete the document form database
   */
  public async destroy() {
    if (!this.data._id) return;

    // Trash The data
    this.data.deletedAt = new Date();

    // create a copy of the current data in the collectionTrash
    // create a copy of the current data in the collectionTrash
    await queryBuilder.create(this.getCollectionName() + "Trash", {
      document: this.data,
      id: this.data.id,
      _id: this.data._id,
    });

    // perform the actual delete
    await queryBuilder.deleteOne(this.getCollectionName(), {
      _id: this.data._id,
    });
  }
```

We performed here before the actual delete from the database a new query to create a copy of the document in the trash collection which will be added in the Model's collection name with the `Trash` suffix.

The content that we added in the collection, contains the entire document data, the id and the `_id` of the document.

But why would we add the id and the `_id` of the document?

Well, we'll need them later to restore the document.

## Restoring The Document

Now let's go to the restoring process, we'll create a new method called `restore` which will be a static method that can be called directly from the model class itself.

```ts
// src/core/database/model/crud-model.ts
// ...
  /**
   * Update model by the given id
   */
  public static async restore<T>(
    this: ChildModel<T>,
    id: PrimaryIdType,
  ): Promise<T | null> {
    // find the document in the trash collection
    const document = await queryBuilder.first(this.collectionName + "Trash", {
      [this.primaryIdColumn]: id,
    });

    // if no document, then it means there is no document in the trash
    // table with that id
    if (!document) return null;

    // if we got here, create a new model and inject to it
    // the document object
    const model = this.self(document.document);

    // add restoredAt column with current date
    model.set("restoredAt", new Date());

    // perform save
    await model.save();

    // return the model
    return model;
  }
```

The restore method receives one parameter, the id of the document that we want to restore from the trash, if the document exists in the trash collection, get it.

Now if the document exists, create a new model, inject the `document` which contains the entire data of the deleted document, after that add the `restoredAt` column with the current date.

Now we can perform the save, and return the model.

But there is a catch here, remember in the `save` method we have a condition that checks if the document has an `_id` or not, if it has an `_id` then it means that the document already exists in the database, so we'll perform an update, but if it doesn't have an `_id` then we'll perform an insert.

So we need to remove the `_id` from the document before we perform the save, so we'll do that in the `restore` method.

So we'll add another condition to check if it is being restored.

We'll created a property called `isRestored` and method to mark the model as restored.

```ts
// src/core/database/model/model.ts
// ...

  /**
   * A flag to determine whether the model is restored or not
   */
  protected isRestored = false;
  /**
   * Mark the model as restored
   */
  public markAsRestored() {
    this.isRestored = true;
    return this;
  }
```

Now let's call it in the `restore` method.

```ts
// src/core/database/model/crud-model.ts
// ...
  /**
   * Update model by the given id
   */
  public static async restore<T>(
    this: ChildModel<T>,
    id: PrimaryIdType,
  ): Promise<T | null> {
    // find the document in the trash collection
    const document = await queryBuilder.first(this.collectionName + "Trash", {
      [this.primaryIdColumn]: id,
    });

    // if no document, then it means there is no document in the trash
    // table with that id
    if (!document) return null;

    // if we got here, create a new model and inject to it
    // the document object
    const model = this.self(document.document);

    // add restoredAt column with current date
    model.set("restoredAt", new Date());

    // mark the model as restored
    model.markAsRestored();

    // perform save
    await model.save();

    // return the model
    return model;
  }
```

Now let's go to the `save` method and add the condition.

```ts
// src/core/database/model/model.ts


  /**
   * Perform saving operation either by updating or creating a new record in database
   */
  public async save(mergedData: Document = {}) {
    this.merge(mergedData);

    // check if the data contains the primary id column
    // but is not being restored
    if (this.data._id && !this.isRestored) {
      // perform an update operation
      // check if the data has changed
      // if not changed, then do not do anything
      if (areEqual(this.originalData, this.data)) return;

      this.data.updatedAt = new Date();

      await queryBuilder.update(
        this.getCollectionName(),
        {
          _id: this.data._id,
        },
        this.data,
      );

      // unset the isRestored flag
      if (this.isRestored) {
        this.isRestored = false;
      }
    } else {
      // creating a new document in the database
      const generateNextId =
        this.getStaticProperty("generateNextId").bind(Model);

      // if the column does not exist, then create it
      if (!this.data.id) {
        this.data.id = await generateNextId();
      }

      const now = new Date();

      // if the column does not exist, then create it
      if (!this.data.createdAt) {
        this.data.createdAt = now;
      }

      // if the column does not exist, then create it
      if (!this.data.updatedAt) {
        this.data.updatedAt = now;
      }

      this.data = await queryBuilder.create(
        this.getCollectionName(),
        this.data,
      );
    }
  }
```

And that's it, now we can restore the document.

```ts
import User from './models/users';

const user = await User.create({
    name: 'John Doe',
});

await user.destroy();

const restoredUser = await User.restore(user.data.id);

console.log(restoredUser.data); // { id: 1, name: 'John Doe', createdAt: '2021-01-01', updatedAt: '2021-01-01', restoredAt: '2021-01-01', deletedAt: '2021-01-01', _id: '5ff5c1c0e4b0e8b8b8b8b8b8' }
```

And that's it!

## 🎨 Conclusion

In this article, we learned how to implement the recycle bin and move the model to it and learnt how to restore it again.

## 🚀 Project Repository

You can find the latest updates of this project on [Github](https://github.com/hassanzohdy/nodejs-2023)

## 😍 Join our community

Join our community on [Discord](https://discord.gg/pb2vmdfhGf) to get help and support (Node Js 2023 Channel).

## 🎞️ Video Course (Arabic Voice)

If you want to learn this course in video format, you can find it on [Youtube](https://www.youtube.com/playlist?list=PLGO8ntvxgiZMJc7RN2lIq9WmMOlWZGzmz), the course is in Arabic language.

## 💰 Bonus Content 💰

You may have a look at these articles, it will definitely boost your knowledge and productivity.

General Topics

- [Event Driven Architecture: A Practical Guide in Javascript](https://dev.to/hassanzohdy/event-driven-architecture-the-best-paradigm-that-i-love-to-work-with-in-javascript-and-node-js-1gnk)
- [Best Practices For Case Styles: Camel, Pascal, Snake, and Kebab Case In Node And Javascript](https://dev.to/hassanzohdy/best-practices-for-case-styles-camel-pascal-snake-and-kebab-case-in-node-and-javascript-55oi)
- [After 6 years of practicing MongoDB, Here are my thoughts on MongoDB vs MySQL
](https://dev.to/hassanzohdy/after-6-years-of-practicing-mongodb-here-are-my-thoughts-on-mongodb-vs-mysql-574b)

Packages & Libraries

- [Collections: Your ultimate Javascript Arrays Manager](https://dev.to/hassanzohdy/collections-your-ultimate-javascript-array-handler-3o15)
- [Supportive Is: an elegant utility to check types of values in JavaScript](https://dev.to/hassanzohdy/supportive-is-an-elegant-utility-to-check-types-of-values-in-javascript-1b3e)
- [Localization: An agnostic i18n package to manage localization in your project](https://dev.to/hassanzohdy/mongez-localization-the-simplest-way-to-translate-your-website-regardless-your-favorite-framework-4gi3)

 React Js Packages

- [useFetcher: easiest way to fetch data in React Js](https://dev.to/hassanzohdy/usefetcher-the-easiest-way-to-fetch-data-in-react-45o9)

Courses (Articles)

- [React Js: Let"s Create File Manager With React Js and Node Js](https://dev.to/hassanzohdy/lets-create-a-file-manager-from-scratch-with-react-and-typescript-chapter-i-a-good-way-to-expand-your-experience-5g4k)