39-Nodejs Course 2023: Using Query Builder In Crud Model
Hasan Zohdy
Posted on November 5, 2022
In our previous article, we created the Query Builder which performs general operations over MongoDB. In this article, we will use the Query Builder in our CRUD model.
Using Query Builder in CurdModel
Now we've finished the query builder operations, let's use it directly in the CurdModel
.
Let's start with create
method.
Create Method
Let's update the create method to use the query builder directly.
// src/core/database/model/curd-model.ts
import queryBuilder from "../query-builder/query-builder";
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 modelData = { ...data };
// generate a new id
modelData.id = await this.generateNextId();
// perform the insertion
// please note that the create method returns directly the document with `_id` attached to it.
const result = await queryBuilder.create(this.collectionName, modelData);
return this.self(result as ModelDocument);
}
}
We generated the id and then we called the create
method of the query builder which takes the collection name and the data and returns the document with _id
column as well.
Update Method
Let's update the update method to use the query builder directly as well.
// src/core/database/model/curd-model.ts
import queryBuilder from "../query-builder/query-builder";
export default abstract class CrudModel extends BaseModel {
// ...
/**
* Update model by the given id
*/
public static async update<T>(
this: ChildModel<T>,
id: PrimaryIdType,
data: Document,
): Promise<T | null> {
// perform the update
const result = await queryBuilder.update(
this.collectionName,
{ [this.primaryIdColumn]: id },
data,
);
return result ? this.self(result as ModelDocument) : null;
}
We passed the collection name, the filter object, which we'll sue the primary id column and the value will be the given id, and the data to update.
If there is a result coming, then the update has been done, otherwise it will return null
.
Thus we updated the return type of the method to return an instance of class if the update has been done, otherwise it will return null
.
Replace Method
Now let's update the replace method.
// src/core/database/model/curd-model.ts
import queryBuilder from "../query-builder/query-builder";
export default abstract class CrudModel extends BaseModel {
// ...
/**
* 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> {
// perform the replace
const result = await queryBuilder.replace(
this.collectionName,
{ [this.primaryIdColumn]: id },
data,
);
return result ? this.self(result as ModelDocument) : null;
}
Pretty much straightforward, we passed the collection name, the filter object, which we'll sue the primary id column and the value will be the given id, and the data to replace.
Upsert Method
Moving to upsert
method.
// src/core/database/model/curd-model.ts
import queryBuilder from "../query-builder/query-builder";
export default abstract class CrudModel extends BaseModel {
// ...
/**
* Update the document if it exists, otherwise create a new one
*/
public static async upsert<T>(
this: ChildModel<T>,
id: PrimaryIdType,
data: Document,
): Promise<T> {
// perform the upsert
const result = await queryBuilder.upsert(
this.collectionName,
{ [this.primaryIdColumn]: id },
data,
);
return this.self(result as ModelDocument);
}
FindBy Method
Now let's update the findBy
method.
// src/core/database/model/curd-model.ts
import queryBuilder from "../query-builder/query-builder";
export default abstract class CrudModel extends BaseModel {
// ...
/**
* Find document by the given column and value
*/
public static async findBy<T>(
this: ChildModel<T>,
column: string,
value: any,
): Promise<T | null> {
const result = await queryBuilder.first(this.collectionName, {
[column]: value,
});
return result ? this.self(result as ModelDocument) : null;
}
List Method
Now let's update the list
method.
// src/core/database/model/curd-model.ts
import queryBuilder from "../query-builder/query-builder";
export default abstract class CrudModel extends BaseModel {
// ...
/**
* List multiple documents based on the given filter
*/
public static async list<T>(
this: ChildModel<T>,
filter: Filter = {},
): Promise<T[]> {
const result = await queryBuilder.list(this.collectionName, filter);
return result.map(item => this.self(item as ModelDocument));
}
So far nothing is hard to keep up with, we're just replacing the code to use the query builder directly.
Paginate Method
Now let's go to the paginate
method.
// src/core/database/model/curd-model.ts
import queryBuilder from "../query-builder/query-builder";
export default abstract class CrudModel extends BaseModel {
// ...
/**
* Paginate multiple documents based on the given filter
*/
/**
* Paginate records based on the given filter
*/
public static async paginate<T>(
this: ChildModel<T>,
filter: Filter = {},
page = 1,
limit = 15,
): Promise<PaginationListing<T>> {
const documents = await queryBuilder.list(
this.collectionName,
filter,
query => {
query.skip((page - 1) * limit).limit(limit);
},
);
const totalDocumentsOfFilter = await queryBuilder.count(
this.collectionName,
filter,
);
const result: PaginationListing<T> = {
documents: documents.map(document =>
this.self(document as ModelDocument),
),
paginationInfo: {
limit,
page,
result: documents.length,
total: totalDocumentsOfFilter,
pages: Math.ceil(totalDocumentsOfFilter / limit),
},
};
return result;
}
Now let's see what we did here, let's split it into three parts.
I made a small and new update, is setting default values for pagination, for example the filter will be an empty object, page number will default to 1
and limit will be 15
.
Then we called the list
method of the query builder, we passed the collection name, the filter, and a callback function which will be called with the query object, so we can modify the query object, for example, we can skip some records and limit the number of records.
Then we called the count
method of the query builder, we passed the collection name and the filter, and we got the total number of documents of the given filter.
Then we created the result object, we mapped the documents to the model class, and we returned the result.
Actually you know, we might add another feature in the BaseModel to set the default limit per page.
// src/core/database/model/base-model.ts
export default abstract class BaseModel {
// ...
/**
* Document per page
*/
public static perPage = 15;
}
Now let's use it in the paginate
method.
// src/core/database/model/curd-model.ts
import queryBuilder from "../query-builder/query-builder";
export default abstract class CrudModel extends BaseModel {
// ...
/**
* Paginate multiple documents based on the given filter
*/
/**
* Paginate records based on the given filter
*/
public static async paginate<T>(
this: ChildModel<T>,
filter: Filter = {},
page = 1,
limit = this.perPage,
): Promise<PaginationListing<T>> {
// ...
}
So we can now use it lie this:
const result = await User.paginate(); // no filter and limit items per page will be 15
Delete Method
Moving to the delete
method.
As you can recall, we're performing two operations here, one for deleting single document, and the other for deleting multiple documents.
// src/core/database/model/curd-model.ts
import queryBuilder from "../query-builder/query-builder";
export default abstract class CrudModel extends BaseModel {
// ...
/**
* Delete single document if the given filter is an ObjectId of mongodb
* Otherwise, delete multiple documents based on the given filter object
*/
public static async delete<T>(
this: ChildModel<T>,
filter: PrimaryIdType | Filter,
): Promise<number> {
if (
filter instanceof ObjectId ||
typeof filter === "string" ||
typeof filter === "number"
) {
return queryBuilder.deleteOne(this.collectionName, {
[this.primaryIdColumn]: filter,
});
}
return await queryBuilder.delete(this.collectionName, filter);
}
Pretty much nice, right?
🎨 Conclusion
In this article, we learned how to use the query builder directly in the crud model, which reduces the code and makes it more readable.
🚀 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 5, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.