33-Nodejs Course 2023: Database Models: Crud Operations: Update Operations
Hasan Zohdy
Posted on November 3, 2022
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>);
}
}
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);
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>);
}
}
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);
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>);
}
}
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);
🎨 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
- Event Driven Architecture: A Practical Guide in Javascript
- Best Practices For Case Styles: Camel, Pascal, Snake, and Kebab Case In Node And Javascript
- After 6 years of practicing MongoDB, Here are my thoughts on MongoDB vs MySQL
Packages & Libraries
- Collections: Your ultimate Javascript Arrays Manager
- Supportive Is: an elegant utility to check types of values in JavaScript
- Localization: An agnostic i18n package to manage localization in your project
React Js Packages
Courses (Articles)
Posted on November 3, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.