36-Nodejs Course 2023: Database Master Mind
Tags: node, typescript, mongodb, fastify
Published: 2022-11-04 18:59:10

---
title: 36-Nodejs Course 2023: Database Master Mind
published: true
description: 
series: Nodejs Course 2023
tags: nodejs, typescript, mongodb, fastify
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/61tcz2ey0t85yjumjlve.png
# Use a ratio of 100:42 for best results.
---

Now we've seen how to operate with database CRUD operations, where we used the `_id` field to identify the document. But what if we want to use a different field to identify to have an auto incremented id with an integer value? In this article, we will see how to do that.

## Auto Incremented Id

So how this works? We'll create a database collection (table), i'll call it `MasterMinder`, that collection we'll use as our base collection to store for now two columns, the collection name that we want to auto increment the id and the last id that we used.

## How this works

When we create a new record in the database using `create` method we've created, we'll add a column internally called `id`, what we'll need here is to get the last id of the current Model's collection name from the `MasterMind` collection, if that collection was not created yet, we'll create it and set the last id to `1` or to equal the `initialValue` that we'll add as a feature later, then we'll increment the last id by 1 and save it in the `MasterMind` collection.

## Implementation

So let's start by creating a new file called `master-mind.ts` inside `src/core/database/model` directory, and add the following code:

```ts
// src/core/database/model/master-mind.tsimport connection, { Connection } from "./../connection";

export class MasterMind {
  /**
   * Database Connection
   */
  public connection: Connection = connection;

  /**
   * Get the last id of the collection
   */
  public async getLastId(collectionName: string): Promise<number> {
    // get the collection query
    const masterMind = await this.connection.database.collection("MasterMind");
    // find the record of the given collection name
    const masterMindData = await masterMind.findOne({ collectionName });

    // if the record exists, then return it
    if (masterMindData) {
      return masterMindData.id;
    }

    // if the record does not exist, then return zero
    return 0;
  }

  /**
   * Increment and return the last updated id for the given collection
   * If the collection does not exist, then create it and return the initial value
   */
  public async generateNextId(
    collectionName: string,
    incrementBy = 1,
    initialValue = 1,
  ): Promise<number> {
    // get the collection query
    const masterMind = await this.connection.database.collection("MasterMind");

    // find the record of the given collection name
    const masterMindData = await masterMind.findOne({ collectionName });

    // if the record exists, then increment the id by the given incrementBy value
    if (masterMindData) {
      const lastId = masterMindData.id + incrementBy;
      // update the record with the new id
      await masterMind.updateOne({ collectionName }, { $set: { id: lastId } });
      // return the new id
      return lastId;
    }

    // if the record does not exist, then create it with the given initial value
    await masterMind.insertOne({ collectionName, id: initialValue });

    // return the initial value
    return initialValue;
  }
}

const masterMind = new MasterMind();

export default masterMind;
```

So we have created a class that will operate with two operations, one just to getting the last id, which will return `0` if the collection does not exist, this case is not usually happens but it's good to have it, and the second operation is to generate the next id, which will return the initial value if the collection does not exist, and will increment the last id by the given `incrementBy` value.

Now let's go to our model and apply the changes.

```ts
// src/core/database/model/model.ts
import masterMind from './master-mind';

export default abstract class Model {
  // ...
  /**
   * Define the initial value for the auto incremented id
   */
  public static initialId = 1;

  /**
   * define the increment by value for the auto incremented id
   */
  public static incrementBy = 1;

  /**
   * Generate and return next id of current collection
   */
  public static async generateNextId() {
    return await masterMind.generateNextId(
      this.collectionName,
      this.incrementBy,
      this.initialId,
    );
  }

  /**
   * 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: BaseModel<T>,
    data: Record<string, any>,
  ): Promise<T> {
    // 1- get the query of the collection
    const query = this.query();

    const modelData = { ...data };

    // check if the data does not have an id column
    if (!modelData.id) {
      modelData.id = await this.generateNextId();
    }

    // perform the insertion
    const result = await query.insertOne(data);

    // associate the mongodb _id as well
    modelData._id = result.insertedId;

    return this.self(modelData);
  }
}
```

What we did here is we added two static variables, one for the initial value and the other for the increment by value, and we used them in the `create` method, but we checked first if the data that is coming does not have an id column, as in some rare situations the id is being generated outside the model, and we don't want to override it, so if there is no id, then we'll generate one.

Now let's give it a try.

```ts
// src/app/users/routes.ts
import User from "./models/user";

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

  console.log(user.data);
}, 4000);
```

Now you will see something like this:

![Generated id](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oe9565w7o2c5s0mmo3yh.png)

If you just saved the file again, it will generate a new record with a new id, and if you check the `MasterMind` collection, you will see that the id has been incremented.

## Updating methods to use the new id

As you can tell, we were using MongoDB's `_id` as the id of the record, but now we are using our own id, so we need to update the methods to use the new id.

You know, actually we can make it more advanced, we can allow the user to define what is the primary key that we use as `id` in the finding methods, but for now, we'll just use the new id.

```ts
// src/core/database/model/model.ts

export default abstract class Model {
  // ....  
  /**
   * Primary column
   */
  public static primaryColumn = "id";
}
```

We defined a new static variable called `primaryColumn`, and we set it to `id` by default, so now we can use it in any method that deals with the id, and also we'll change the type of the id to accept both `string` and `number`.

```ts
// src/core/database/model/model.ts

// update method

  /**
   * Update model by the given id
   */
  public static async update<T>(
    this: BaseModel<T>,
    // 👇🏻 replace the type of string to be string | number or ObjectId
    id: string | ObjectId | number,
    data: Record<string, any>,
  ): Promise<T> {
    // get the query of the current collection
    const query = this.query();

    // execute the update operation

    // 👇🏻 replace the _id column with the primary column
    const filter = {
      [this.primaryColumn]: id,
    };

    const result = await query.findOneAndUpdate(
      filter,
      {
        $set: data,
      },
      {
        returnDocument: "after",
      },
    );

    return this.self(result.value as Record<string, any>);
  }
  
  // replace method
  
  /**
   * Replace the entire document for the given document id with the given new data
   */
  public static async replace<T>(
    this: BaseModel<T>,
    // 👇🏻 replace the type of string to be string | number or ObjectId
    id: string | ObjectId | number,
    data: Record<string, any>,
  ): Promise<T> {
    const query = this.query();

    // 👇🏻 replace the _id column with the primary column
    const filter = {
      [this.primaryColumn]: id,
    };

    const result = await query.findOneAndReplace(filter, data, {
      returnDocument: "after",
    });

    return this.self(result.value as Record<string, any>);
  }

  // find method
  
  /**
   * Find document by id
   */
    // 👇🏻 replace the type of string to be string | number or ObjectId
  public static async find<T>(this: BaseModel<T>, id: string | ObjectId | number) {
    // 👇🏻 replace the _id column with the primary column
    return this.findBy(this.primaryColumn, id);
  }
```

Now regarding the delete method, the type we were using using is ObjectId or an object, so we'll add also the number type.

In the check part, we'll check if the given value is `ObjectId` `string` or `number` , then we'll perform a filter by multiple columns, otherwise, we'll perform a filter by the primary column.

```ts
// src/core/database/model/model.ts

  /**
   * 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: BaseModel<T>,
    filter: string | ObjectId | number | Record<string, any>,
  ): Promise<number> {
    const query = this.query();

    if (
      filter instanceof ObjectId ||
      typeof filter === "number" ||
      typeof filter === "string"
    ) {
      const result = await query.deleteOne({
        [this.primaryColumn]: filter,
      });

      return result.deletedCount;
    }

    const result = await query.deleteMany(filter);

    return result.deletedCount;
  }
```

## Our last remaining question

What if the user's id now is `2` then the next one will be `3`, what happens if the user with id `3` is deleted, what will be the next id that will be generated?, of course it will be `4` as the id will not stop at the deleted ones but will go from the last saved value in the `MasterMind` collection.

## 🎨 Conclusion

In this article, we learned how to generate an auto-increment id for our MongoDB documents, and we also learned how to use it in our models, and we also learned how to update the methods to use the new id.

In our next article, it will be another refactoring article, we'll refactor the `Model` class to be more generic and more reusable, and we'll also add some new features to it.

## 🚀 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)