DEV Community

Hasan Zohdy
Hasan Zohdy

Posted on

33-Nodejs Course 2023: Database Models: Crud Operations: Update Operations

So, we have created our first method successfully which is the create method.

Now let's work with some update operations, let's start with the basic one which is the update method.

Update

The update method will receive an id and an object of data that will be updated in the database and return a new model instance with the data from the database.

// src/core/database/model/model.ts
import { Collection } from "mongodb";
import connection, { Connection } from "../connection";
import { Database } from "../database";

/**
 * Base Model type that we need to define
 * to tell typescript that we're going to return a child that extends that base model
 * which will be the T (The child class)
 */
type BaseModel<T> = typeof Model & (new () => T);

export default abstract class Model {
  // ...

  /**
   * Update the given data to the given id and return a new instance of the model
   */
  public static async update<T>(
    this: BaseModel<T>,
    id: string | number,
    data: Record<string, any>,
  ): Promise<T> {
    // 1- get the query of the collection
    const query = this.query();

    // 2- create a filter object to filter the data by the id
    const filter = {
      _id: id,
    };

    // 3- we'll use findOneAndUpdate to get the record after the update is done
    const result = await query.findOneAndUpdate(
      filter,
      {
        // Update the given data
        $set: data,
      },
      {
        // return the entire updated document after update
        returnDocument: "after",
      },
    );

    // 4- return a new instance of the model with the updated data
    return this.self(result.value as Record<string, any>);
  }
}
Enter fullscreen mode Exit fullscreen mode

Pretty much the same as create workflow, it receives the _id (for now) and the data that will be updated.

Internally, we use findOneAndUpdate to update the data and return the updated document.

First we create a filter object that we want to run the update on, then we use findOneAndUpdate to update the data and return the updated document.

It accepts three arguments, the first one is the filter we want to search by for the document which will be searching by the _id column, the second argument is an object of the update operations (there are multiple cases that can be used in this one) but we'll only use the $set operator, which is a built on operator on MongoDB to tell it to update the given data, the third argument is an object of options, we'll use the returnDocument option to tell it to return the updated document after the update is done.

Then we return a new instance of the model with the updated data.

Now let's give it a try

// src/app/users/routes.ts

setTimeout(async () => {
  const user = await User.create({
    name: "hasan",
    email: "hassanzohdy@gmail.com",
  });

  const updatedUser = await User.update(user.data._id
    {
      name: "hasanZ",
      age: 25,
    },
  );

  console.log(updatedUser.data); // { _id: "12wqedrt42ewqsdefrt5213", age: 25, name: "hasanZ", email: "hassanzohdy@gmail.com"}
}, 4000);
Enter fullscreen mode Exit fullscreen mode

As you can see, we created a user, then we updated the name and added a new field/column which is age of the user and we got the updated user.

Now what if we want to replace the entire document with the new data?

We can do that by using the replace method.

Replace

The previous one we made an update on the document by updating the existing column and added new one, but if we want to replace it entirely with the new data, we can use the findOneAndReplace method.

// src/core/database/model/model.ts
import { Collection } from "mongodb";
import connection, { Connection } from "../connection";
import { Database } from "../database";

/**
 * Base Model type that we need to define
 * to tell typescript that we're going to return a child that extends that base model
 * which will be the T (The child class)
 */
type BaseModel<T> = typeof Model & (new () => T);

export default abstract class Model {
  // ...

  /**
   * Replace the given data to the given id and return a new instance of the model
   */
  public static async replace<T>(
    this: BaseModel<T>,
    id: string | number,
    data: Record<string, any>,
  ): Promise<T> {
    // 1- get the query of the collection
    const query = this.query();

    // 2- create a filter object to filter the data by the id
    const filter = {
      _id: id,
    };

    // 3- we'll use findOneAndReplace to get the record after the replace is done
    const result = await query.findOneAndReplace(
      filter,
      data,
      {
        // return the entire updated document after update
        returnDocument: "after",
      },
    );

    // 4- return a new instance of the model with the updated data
    return this.self(result.value as Record<string, any>);
  }
}
Enter fullscreen mode Exit fullscreen mode

If you see it is almost the same as the update method, the only difference is that we use findOneAndReplace instead of findOneAndUpdate and we don't use the $set operator.

Now let's give it a try

// src/app/users/routes.ts

setTimeout(async () => {
  const user = await User.create({
    name: "hasan",
    email: "hassanzohdy@gmail.com",
  });

  const updatedUser = await User.replace(user.data._id
    {
      name: "hasanZ",
      age: 25,
    },
  );

  console.log(updatedUser.data); // { _id: "12wqedrt42ewqsdefrt5213", age: 25, name: "hasanZ"}
}, 4000);
Enter fullscreen mode Exit fullscreen mode

The main difference is that we don't have the email field/column anymore.

Upsert

Now what if we want to update the document if it exists, or create a new one if it doesn't exist?

We can do that by using the findOneAndUpdate method with the upsert option.

The upsert term is a portmanteau of update and insert. It is used to describe the process of updating a document if it exists or inserting a document if it does not.

// src/core/database/model/model.ts
import { Collection } from "mongodb";
import connection, { Connection } from "../connection";
import { Database } from "../database";

/**
 * Base Model type that we need to define
 * to tell typescript that we're going to return a child that extends that base model
 * which will be the T (The child class)
 */
type BaseModel<T> = typeof Model & (new () => T);

export default abstract class Model {
  // ...

  /**
   * Update the given data to the given id or create a new one if it doesn't exist
   */
  public static async upsert<T>(
    this: BaseModel<T>,
    filter: Record<string, any>,
    data: Record<string, any>,
  ): Promise<T> {
    // 1- get the query of the collection
    const query = this.query();

    // 2- we'll use findOneAndUpdate to get the record after the update is done
    const result = await query.findOneAndUpdate(
      filter,
      {
        // Update the given data
        $set: data,
      },
      {
        // return the entire updated document after update
        returnDocument: "after",
        // upsert the document if it doesn't exist
        upsert: true,
      },
    );

    // 4- return a new instance of the model with the updated data
    return this.self(result.value as Record<string, any>);
  }
}
Enter fullscreen mode Exit fullscreen mode

The upsert method accepts two arguments, the first one is the filter we want to search by for the document, the second argument is an object of the update operations (there are multiple cases that can be used in this one) but we'll only use the $set operator, which is a built on operator on MongoDB to tell it to update the given data.

Then we return a new instance of the model with the updated data.

Now let's give it a try

// src/app/users/routes.ts

setTimeout(async () => {
  const updatedUser = await User.upsert({
    _id: 'some-id',
  },
    {
      name: "hasanZ",
      age: 25,
    },
  );

  console.log(updatedUser.data); // { _id: "some-id", age: 25, name: "hasanZ", email: "hassanzohdy@gmail.com"
}, 4000);
Enter fullscreen mode Exit fullscreen mode

🎨 Conclusion

In this article, we learned update update operations, not all of it but the ones that we need for now like normal update, full replace and upsert.

🎨 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)

Oldest comments (0)