DEV Community

Jayvee Ramos
Jayvee Ramos

Posted on

3. Building a Common Repository for Nest.js Microservices

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;
}

Enter fullscreen mode Exit fullscreen mode

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;
  }
}

Enter fullscreen mode Exit fullscreen mode

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';

Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)