3. Building a Common Repository for Nest.js Microservices
Tags: mongodb, nestjs, typeorm, typescript
Published: 2023-11-07 03:25:15

## Introduction:

In a microservices architecture, maintaining consistent CRUD functionality across different services can be challenging. To address this, we can create an abstract repository in Nest.js that provides common database operations. In this tutorial, we'll walk you through creating an abstract schema and an abstract repository that your microservices can extend, thus saving you from duplicating code.

## Prerequisites:

- Basic knowledge of Nest.js
- Mongoose for MongoDB

## Part 1: Creating the Abstract Schema
In the first part, we will create an abstract schema that will serve as the foundation for other schemas in your microservices.

let's create it inside libs/common/src/database just run the following command in your terminal:

`touch libs/common/src/database/abstract.schema.ts`

populate it with the following codes:

```
import { Prop, Schema } from '@nestjs/mongoose';
import { SchemaTypes } from 'mongoose';
import { ObjectId } from 'mongoose';

@Schema()
export class AbstractDocument {
  @Prop({ type: SchemaTypes.ObjectId })
  _id: ObjectId;
}

```
**In this code:**

We define an abstract class AbstractDocument with a property _id, which is of type ObjectId.
The **@Schema()** decorator comes from Nest.js Mongoose.
The **ObjectId** type is imported from Mongoose.

## Part 2: Building the Abstract Repository

Now, let's create the abstract repository that all other repositories in your microservices will extend.

let's create it inside libs/common/src/database just run the following command in your terminal:

`touch libs/common/src/database/abstract.repository.ts`

populate it with the following codes:

```
import { Model, Types, FilterQuery, UpdateQuery } from 'mongoose';
import { Logger, NotFoundException } from '@nestjs/common';
import { AbstractDocument } from './abstract.schema';

export abstract class AbstractRepository<TDocument extends AbstractDocument> {
  protected readonly logger: Logger;

  constructor(protected readonly model: Model<TDocument>) {
    this.logger = new Logger(model.modelName);
  }

  async create(document: Omit<TDocument, '_id'>): Promise<TDocument> {
    const createdDocument = new this.model({
      ...document,
      _id: new Types.ObjectId(),
    });

    const savedDocument = await createdDocument.save();
    return savedDocument.toJSON() as unknown as TDocument;
  }

  async findOne(filterQuery: FilterQuery<TDocument>): Promise<TDocument> {
    const document = (await this.model
      .findOne(filterQuery)
      .lean(true)) as TDocument;
    if (!document) {
      this.logger.warn(
        `Document not found with filter query: ${JSON.stringify(filterQuery)}`,
      );
      throw new NotFoundException('The document was not found');
    }
    return document;
  }

  async findOneAndUpdate(
    filterQuery: FilterQuery<TDocument>,
    update: UpdateQuery<TDocument>,
  ): Promise<TDocument> {
    const document = (await this.model
      .findOneAndUpdate(filterQuery, update, { new: true })
      .lean(true)) as TDocument;
    if (!document) {
      this.logger.warn(
        `Document not found with filter query: ${JSON.stringify(filterQuery)}`,
      );
      throw new NotFoundException('The document was not found');
    }
    return document;
  }

  async find(filterQuery: FilterQuery<TDocument>): Promise<TDocument[]> {
    const documents = (await this.model
      .find(filterQuery)
      .lean(true)) as TDocument[];
    return documents;
  }
  FilterQuery;
  async findOneAndDelete(
    filterQuery: FilterQuery<TDocument>,
  ): Promise<TDocument> {
    const document = (await this.model
      .findOneAndDelete(filterQuery)
      .lean(true)) as TDocument;
    if (!document) {
      this.logger.warn(
        `Document not found with filter query: ${JSON.stringify(filterQuery)}`,
      );
      throw new NotFoundException('The document was not found');
    }
    return document;
  }
}

```
**In this code:**

We define an abstract class **AbstractRepository** with a generic type TDocument that extends Document.

The repository provides common CRUD functionality, such as **create**, **findOne**, **findOneAndUpdate**, **find**, and **findOneAndDelete**.

We utilize the lean(true) method to retrieve plain JavaScript objects, reducing the overhead of Mongoose's hydrated documents.

don't forget to open your `libs/common/src/database/index.ts `
and export the two newly created files
your `index.ts` should look like this 

```
export * from './database.module';
export * from './abstract.schema';
export * from './abstract.repository';

```
## Conclusion:
By creating an abstract schema and an abstract repository, you can ensure consistent CRUD operations across your Nest.js microservices. This approach simplifies your codebase, reduces duplication, and provides a common foundation for your services.

Extend the AbstractRepository in your custom repositories to benefit from its built-in CRUD functionality, and you'll have a robust architecture for your microservices.