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)