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
1. user.entity.ts
β Domain Entity
export interface User {
id: string;
name: string;
email: string;
createdAt: Date;
}
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>;
}
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);
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;
}
}
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 });
}
}
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);
})();
β 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)