DEV Community

Mofajjal Rasul
Mofajjal Rasul

Posted on • Edited on

A Generic Repository Pattern for NestJS with Mongoose for MongoDB

This analysis examines a sophisticated generic repository pattern for MongoDB integration in NestJS applications, demonstrating industry-standard practices for database abstraction, error mitigation, and type-safe query orchestration. The implementation adheres to SOLID principles while maintaining full compatibility with Mongoose's feature set.


Core Implementation

The complete unmodified repository class provides foundational CRUD operations:

import { ConflictException, Logger, NotFoundException } from "@nestjs/common"; import { ObjectId } from "mongodb"; import { Document, FilterQuery, FlattenMaps, Model, QueryOptions, SaveOptions, UpdateQuery, UpdateWithAggregationPipeline, } from "mongoose"; export class GenericRepository<T extends Document> { private readonly internalLogger: Logger; private readonly internalModel: Model<T>; constructor(model: Model<T>, logger?: Logger) { this.internalModel = model; this.internalLogger = logger || new Logger(this.constructor.name); } async create(doc: Partial<T>, saveOptions: SaveOptions = {}): Promise<T> { try { const createdEntity = new this.internalModel(doc); const savedResult = await createdEntity.save(saveOptions); return savedResult; } catch (error) { if (error?.name === "MongoServerError" && error?.code === 11000) { this.internalLogger.error("Duplicate key error while creating:", error); throw new ConflictException("Document already exists with provided inputs"); } throw error; } } async getAll( filter: FilterQuery<T> = {}, options: QueryOptions = {}, ): Promise<FlattenMaps<T>[]> { try { if (!options.sort) { options.sort = { createdAt: -1 }; } const result = await this.internalModel .find(filter, null, options) .lean() .exec(); return result; } catch (error) { this.internalLogger.error("Error finding entities:", error); return []; } } async getOneWhere( filter: FilterQuery<T>, options: QueryOptions = {}, ): Promise<T | null> { try { const result = await this.internalModel .findOne(filter, null, options) .exec(); return result; } catch (error) { this.internalLogger.error("Error finding entity by ID:", error); return null; } } async getOneById(id: string, options: QueryOptions = {}): Promise<T | null> { try { const result = await this.internalModel .findOne({ _id: id }, null, options) .exec(); return result; } catch (error) { this.internalLogger.error("Error finding entity by ID:", error); return null; } } async updateOneById( documentId: string, updated: UpdateWithAggregationPipeline | UpdateQuery<T>, options: QueryOptions = {}, ): Promise<T> { try { const result = await this.internalModel .findOneAndUpdate( { _id: documentId }, { ...updated, updatedAt: new Date() }, { ...options, new: true }, ) .exec(); if (!result) { throw new NotFoundException("Document not found with provided ID"); } return result; } catch (error) { if (error?.name === "MongoServerError" && error?.code === 11000) { this.internalLogger.error("Duplicate key error while updating:", error); throw new ConflictException("Document already exists with provided inputs"); } this.internalLogger.error("Error updating one entity:", error); throw error; } } async removeOneById(id: string): Promise<boolean> { try { const { acknowledged } = await this.internalModel .deleteOne({ _id: id }) .exec(); return acknowledged; } catch (error) { this.internalLogger.error("Error removing entities:", error); throw error; } } async count(filter: FilterQuery<T> = {}): Promise<number> { try { const count = await this.internalModel.countDocuments(filter).exec(); return count; } catch (error) { this.internalLogger.error("Error counting documents:", error); throw error; } } async validateObjectIds(listOfIds: string[] = []): Promise<boolean> { try { if (!Array.isArray(listOfIds) || !listOfIds?.length) { return false; } const objectIdStrings = listOfIds.map(String); const objectIds = objectIdStrings.map((id) => new ObjectId(id)); const result = await this.internalModel .find({ _id: { $in: objectIds } }) .select("_id") .lean() .exec(); return listOfIds.length === result?.length; } catch (error) { this.internalLogger.error("Error during validation:", error); return false; } } } 
Enter fullscreen mode Exit fullscreen mode

Structural Analysis

1. Type-Safe Foundation

  • Generic <T extends Document> parameter ensures collection-specific typing
  • FlattenMaps return type guarantees lean document serialization

2. Error Mitigation Strategy

  • Duplicate key detection (MongoServerError code 11000)
  • Contextual exception conversion (ConflictException/NotFoundException)
  • Fallthrough error propagation for custom handling

3. Query Optimization

  • Automatic createdAt sorting in getAll
  • Lean query execution for reduced memory footprint
  • Selective _id projection in validateObjectIds

Operational Paradigms

  1. Document Lifecycle Management

    • Atomic create/update/delete operations
    • Built-in timestamp management (createdAt/updatedAt)
  2. Validation Protocol

    • Batch ObjectID verification via $in operator
    • Type coercion safeguards for ID lists
  3. Audit Capabilities

    • Count method for aggregate analytics
    • Full query logging for operational transparency

Enterprise Implementation Scenarios

  1. User Management Systems

    • Conflict detection during user registration
    • Secure credential updates via atomic operations
  2. Inventory Control Platforms

    • Bulk product ID validation for order processing
    • Optimized catalog browsing with lean queries
  3. Financial Transaction Logs

    • Immutable audit trails through createdAt sorting
    • Atomic balance updates with rollback safeguards

Architectural Benefits

  1. Cross-Cutting Concerns

    • Centralized logging infrastructure
    • Uniform error handling patterns
  2. Database Agnosticism

    • Abstracted Mongoose implementation details
    • Clear separation between business logic and persistence
  3. Performance Considerations

    • Lean document returns minimize payload size
    • Batch operations reduce database roundtrips

This implementation establishes a robust foundation for enterprise-grade applications, providing:

  • Type Integrity: Compile-time validation of document structures
  • Operational Safety: Transaction-ready method signatures
  • Diagnostic Visibility: Contextual logging throughout data operations

The pattern serves as a blueprint for complex systems requiring maintainable, scalable data access layers while maintaining full compatibility with MongoDB's native capabilities.

Top comments (0)