After reviewing the NestJS documentation and examining their sample source codes, it appears challenging to implement a Repository pattern between the service layer and the database layer (e.g. MongoDB).
In NestJS, database operations are executed directly within the Service class, such as in the CatsService class.
This approach presents several issues:
- Changing the type of database becomes complex due to its usage across different services
- The model (Document Mongo) is utilized in database schemas, service classes, controllers, and even in Resolvers if GraphQL is employed, resulting in the use of Mongo types in various layers which leads to TypeScript complications
- Mixing business logic with database handling logic within the service class (e.g. CatsService)
- And more...
Here's an example from the NestJS documentation showcasing the CatsService:
import { Model } from 'mongoose';
import { Injectable, Inject } from '@nestjs/common';
import { Cat } from './interfaces/cat.interface';
import { CreateCatDto } from './dto/create-cat.dto';
@Injectable()
export class CatsService {
constructor(
@Inject('CAT_MODEL')
private catModel: Model<Cat>,
) {}
async create(createCatDto: CreateCatDto): Promise<Cat> {
const createdCat = new this.catModel(createCatDto);
return createdCat.save();
}
async findAll(): Promise<Cat[]> {
return this.catModel.find().exec();
}
}
The initial problem arises when importing the Model from mongoose in the CatsService:
import { Model } from 'mongoose';
Subsequently, another issue surfaces when returning the Model/Document to the client's layer (controller or resolver):
private catModel: Model<Cat>,
...
async findAll(): Promise<Cat[]> {
return this.catModel.find().exec();
}
As a result of these challenges, the controller or resolver begins using MongoDB Models, creating tight coupling between them and the database along with its types:
import { Cat } from '../graphql.schema';
Furthermore, a complication arises when REST is used instead of GQL with TypeScript:
import { Customer } from './models/customer.model';
An error occurs in the following line:
Conversion of type 'import("c:/Test/src/customer/customer.schema").Customer' to type 'import("c:/Test/src/customer/models/customer.model"). Customer' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
const customer = await this.customerService.findById(id) as Customer;
This discrepancy stems from declaring the Business Model as the return type in the controller while the customerService class returns the MongoDB object.
import { Customer } from './models/customer.model';
Repository Pattern?
To address the aforementioned issues, considering the popular Repository Pattern could be a solution - right? I came across an article discussing the implementation of the repository pattern in NestJS with MongoDB: Implementing a Generic Repository Pattern Using NestJS
However, in my view, this Repository pattern implementation may not always be suitable for real-world scenarios and could be constrained by the drawbacks highlighted in the comments section of that article:
Provides some insights on implementing a generic Repository Pattern efficiently?
Hence, the outlined Repository pattern implementation may not fully align with the demands of a practical system.
Do you resonate with the concerns expressed in the mentioned comment? If so, how can we overcome the challenges to achieve:
A proper implementation of the Repository pattern (especially in MongoDB)
Clear separation of the controller/resolver layer from the service and database layers
The ability to operate on nested documents by their ID, including updating, deleting, and inserting