39-Nodejs Course 2023: Using Query Builder In Crud Model
Tags: node, typescript, mongodb, fastify
Published: 2022-11-05 10:43:07

---
title: 39-Nodejs Course 2023: Using Query Builder In Crud Model
published: true
description: 
series: Nodejs Course 2023
tags: nodejs, typescript, mongodb, fastify
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rrf2frufgnyyr29iwy9w.png
# Use a ratio of 100:42 for best results.
---

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.

```ts
// 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.

```ts
// 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.

```ts
// 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.

```ts
// 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.

```ts
// 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.

```ts
// 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.

```ts
// 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.

```ts
// 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.

```ts
// 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:

```ts
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.

```ts
// 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](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)