DEV Community

Cover image for 43-Nodejs Course 2023: Database Models: Created At And Updated At Columns
Hasan Zohdy
Hasan Zohdy

Posted on

43-Nodejs Course 2023: Database Models: Created At And Updated At Columns

So we saw how we can save our data in the model, but we need to add more details about the saving process like when the document has been created and when it has been updated.

So let's do it.

Create Method

In our static class CrudModel we've the create and update methods, the create method generates a new id before saving the data and returns a new instance of the model.

But what about if we reverse it? we create a new instance with the data and then we just save it!

// src/core/database/model/crud-model.ts

export default abstract class CrudModel extends BaseModel {
  // ...

  /**
   * Create a new record in the database for the current model (child class of this one)
   * and return a new instance of it with the created data and the new generated id
   */
  public static async create<T>(
    this: ChildModel<T>,
    data: Document,
  ): Promise<T> {
    const model = this.self(data);

    await model.save();

    return model;
  }
}
Enter fullscreen mode Exit fullscreen mode

See, much cleaner and easier, also it will prevent code duplicate as we don't have to generate the id in both methods in the create and save methods.

Update Method

Let's update the update method as well

// src/core/database/model/crud-model.ts

export default abstract class CrudModel extends BaseModel {
  // ...

  /**
   * Update model by the given id and return it if found
   */
  public static async update<T>(
    this: ChildModel<T>,
    id: PrimaryIdType,
    data: Document,
  ): Promise<T | null> {
    const model = (await this.find(id)) as any; // silent typescript compiler

    if (!model) return null;

    await model.save(data);

    return model;
  }
}
Enter fullscreen mode Exit fullscreen mode

We first get the model by the given id.

If the model is not found, then we return null.

Otherwise, we update the model with the given data and return it.

Replace Method

Let's do the same with the replace method

  /**
   * Replace the entire document for the given document id with the given new data
   */
  public static async replace<T>(
    this: ChildModel<T>,
    id: PrimaryIdType,
    data: Document,
  ): Promise<T | null> {
    const model = (await this.find(id)) as any;

    if (!model) return null;

    model.replaceWith(data);

    await model.save();

    return model;
  }
Enter fullscreen mode Exit fullscreen mode

We replaced the data using the replaceWith method and then we save it.

Upsert method

Now let's update the upsert method


  /**
   * Find and update the document for the given filter with the given data or create a new document/record
   * if filter has no matching
   */
  public static async upsert<T>(
    this: ChildModel<T>,
    filter: ModelDocument,
    data: Document,
  ): Promise<T | null> {
    let model = (await this.first(filter)) as any;

    // if not exists, create it
    if (!model) {
      model = this.self({ ...filter, ...data });
    } else {
      // update it
      model.replaceWith({ ...filter, ...data });
    }

    await model.save();

    return model;
  }
Enter fullscreen mode Exit fullscreen mode

Here is how it works, we first get the first matched model for the given filter, if the model is not found, then we create a new one with the given filter and data.

Otherwise, we update the model with the given data.

Recapping what we've done

So basically we transformed all of the static operations to act as if we were working with the model directly, this will allow us to put all updates in one place which is the save method only.

Created At And Updated At

Now we need to add the createdAt and updatedAt fields to the model, so let's do it.

The createdAt will be added when the model is being saved as creating, and the updatedAt will be updated every time the model is updated and in the create process as well.

// 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
  if (this.data._id) {
    // 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;

    // update the updated at column
    this.data.updatedAt = new Date();

    await queryBuilder.update(
      this.getCollectionName(),
      {
        _id: this.data._id,
      },
      this.data,
    );
  } else {
    const generateNextId =
      this.getStaticProperty("generateNextId").bind(Model);

    this.data.id = await generateNextId();

    // createdAt Column
    const now = new Date();
    this.data.createdAt = now;
    // updatedAt Column
    this.data.updatedAt = now;

    this.data = await queryBuilder.create(
      this.getCollectionName(),
      this.data,
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

So we added the createdAt and updatedAt columns in the save method, and we also updated the update method to update the updatedAt column.

🎨 Conclusion

So we've added the createdAt and updatedAt columns to the model, and we've also updated the save method to update the updatedAt column.

We also moved all insertion and updating operations to the save method only.

🚀 Project Repository

You can find the latest updates of this project on Github

😍 Join our community

Join our community on Discord 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, 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

Packages & Libraries

React Js Packages

Courses (Articles)

Top comments (0)