DEV Community

Diego Liascovich
Diego Liascovich

Posted on

Implementing the Repository Pattern with Mongoose in Node.js

By Diego Liascovich

Full-Stack Developer | Microservices | Angular | Node.js

The Repository Pattern serves as an intermediary between your application's business logic and the persistence layer (like MongoDB, PostgreSQL, etc.). This abstraction makes your application:

  • Decoupled from the data source
  • Easier to test (you can mock the repository)
  • More maintainable and flexible

๐Ÿงพ Real Case: User Management with MongoDB and Mongoose

We will use TypeScript with Node.js and Mongoose to implement the pattern.

๐Ÿ“ Project Structure

src/
โ”œโ”€โ”€ domain/
โ”‚   โ”œโ”€โ”€ models/
โ”‚   โ”‚   โ””โ”€โ”€ user.entity.ts
โ”‚   โ””โ”€โ”€ repositories/
โ”‚       โ””โ”€โ”€ user.repository.interface.ts
โ”œโ”€โ”€ infrastructure/
โ”‚   โ”œโ”€โ”€ models/
โ”‚   โ”‚   โ””โ”€โ”€ user.model.ts
โ”‚   โ””โ”€โ”€ repositories/
โ”‚       โ””โ”€โ”€ user.repository.ts
โ”œโ”€โ”€ application/
โ”‚   โ””โ”€โ”€ services/
โ”‚       โ””โ”€โ”€ user.service.ts
โ””โ”€โ”€ main.ts
Enter fullscreen mode Exit fullscreen mode

1. user.entity.ts โ€“ Domain Entity

export interface User {
  id: string;
  name: string;
  email: string;
  createdAt: Date;
}
Enter fullscreen mode Exit fullscreen mode

2. user.repository.interface.ts โ€“ Repository Interface

import { User } from '../models/user.entity';

export interface IUserRepository {
  findById(id: string): Promise<User | null>;
  findAll(): Promise<User[]>;
  create(user: Omit<User, 'id' | 'createdAt'>): Promise<User>;
  delete(id: string): Promise<boolean>;
}
Enter fullscreen mode Exit fullscreen mode

3. user.model.ts โ€“ Mongoose Schema and Model

import mongoose, { Schema } from 'mongoose';

const UserSchema = new Schema({
  name: String,
  email: String,
  createdAt: { type: Date, default: Date.now }
});

export const UserModel = mongoose.model('User', UserSchema);
Enter fullscreen mode Exit fullscreen mode

4. user.repository.ts โ€“ Mongoose Implementation

import { IUserRepository } from '../../domain/repositories/user.repository.interface';
import { User } from '../../domain/models/user.entity';
import { UserModel } from '../models/user.model';

export class UserRepository implements IUserRepository {
  async findById(id: string): Promise<User | null> {
    return await UserModel.findById(id).lean();
  }

  async findAll(): Promise<User[]> {
    return await UserModel.find().lean();
  }

  async create(data: Omit<User, 'id' | 'createdAt'>): Promise<User> {
    const created = new UserModel(data);
    const saved = await created.save();
    return saved.toObject();
  }

  async delete(id: string): Promise<boolean> {
    const result = await UserModel.findByIdAndDelete(id);
    return !!result;
  }
}
Enter fullscreen mode Exit fullscreen mode

5. user.service.ts โ€“ Business Logic

import { IUserRepository } from '../../domain/repositories/user.repository.interface';
import { User } from '../../domain/models/user.entity';

export class UserService {
  constructor(private readonly userRepo: IUserRepository) {}

  async listUsers(): Promise<User[]> {
    return await this.userRepo.findAll();
  }

  async registerUser(name: string, email: string): Promise<User> {
    return await this.userRepo.create({ name, email });
  }
}
Enter fullscreen mode Exit fullscreen mode

6. main.ts โ€“ Example Usage

import mongoose from 'mongoose';
import { UserRepository } from './infrastructure/repositories/user.repository';
import { UserService } from './application/services/user.service';

(async () => {
  await mongoose.connect('mongodb://localhost:27017/repository-example');

  const userRepo = new UserRepository();
  const userService = new UserService(userRepo);

  const newUser = await userService.registerUser('Padie78', 'Padie78@example.com');
  console.log('User created:', newUser);

  const allUsers = await userService.listUsers();
  console.log('All users:', allUsers);
})();
Enter fullscreen mode Exit fullscreen mode

โœ… Benefits of This Architecture

  • Decoupling: You can easily switch the database or mock the repository.
  • Unit Testing: You can mock IUserRepository in your tests.
  • Flexibility: Your business logic is not tied to any specific DB implementation.

Top comments (0)