image de chargement
Back to glossary

Repository Pattern

Architectural pattern that encapsulates data access logic, creating an abstraction between business layer and persistence.

Updated on January 10, 2026

The Repository Pattern is an architectural design pattern that acts as an abstraction layer between business logic and data access layer. It centralizes persistence operations (CRUD) by providing a consistent interface, independent of the underlying storage technology. This pattern promotes separation of concerns and greatly facilitates unit testing by enabling the use of mock repositories.

Repository Pattern Fundamentals

  • Complete abstraction of data source (relational DB, NoSQL, API, cache)
  • Domain-oriented business interface rather than technical (findActiveUsers vs SELECT)
  • Centralization of querying logic and object-relational mapping
  • Isolation of business logic from persistence implementation details

Strategic Benefits

  • Enhanced testability: easy replacement with mocks for unit tests
  • Technological flexibility: database changes without impacting business logic
  • Maintainability: query centralization avoiding code duplication
  • Clear separation of responsibilities following SOLID principles
  • Centralized optimization (caching, pagination, filtering) applicable system-wide

Practical Implementation Example

user.repository.ts
// Repository interface (business contract)
interface UserRepository {
  findById(id: string): Promise<User | null>;
  findByEmail(email: string): Promise<User | null>;
  findActiveUsers(limit: number): Promise<User[]>;
  save(user: User): Promise<User>;
  delete(id: string): Promise<void>;
}

// Concrete implementation with Prisma
class PrismaUserRepository implements UserRepository {
  constructor(private prisma: PrismaClient) {}

  async findById(id: string): Promise<User | null> {
    const dbUser = await this.prisma.user.findUnique({
      where: { id },
      include: { profile: true }
    });
    return dbUser ? this.mapToDomain(dbUser) : null;
  }

  async findActiveUsers(limit: number): Promise<User[]> {
    const users = await this.prisma.user.findMany({
      where: { isActive: true },
      take: limit,
      orderBy: { createdAt: 'desc' }
    });
    return users.map(u => this.mapToDomain(u));
  }

  async save(user: User): Promise<User> {
    const data = this.mapToPersistence(user);
    const saved = await this.prisma.user.upsert({
      where: { id: user.id },
      create: data,
      update: data
    });
    return this.mapToDomain(saved);
  }

  // Mapping between DB model and domain entity
  private mapToDomain(dbUser: any): User {
    return new User(
      dbUser.id,
      dbUser.email,
      dbUser.profile?.displayName
    );
  }

  private mapToPersistence(user: User): any {
    return {
      id: user.id,
      email: user.email,
      isActive: user.isActive
    };
  }
}

// Usage in business service
class UserService {
  constructor(private userRepo: UserRepository) {}

  async activateUser(userId: string): Promise<void> {
    const user = await this.userRepo.findById(userId);
    if (!user) throw new Error('User not found');
    
    user.activate(); // Pure business logic
    await this.userRepo.save(user);
  }
}

Step-by-Step Implementation

  1. Define repository interface with business-oriented methods (avoid generic SELECTs)
  2. Create domain entities independent of database model
  3. Implement concrete repository with chosen persistence technology
  4. Add mapping methods between DB models and domain entities
  5. Inject repository into business services via dependency inversion
  6. Create mock repositories for unit testing
  7. Centralize query patterns (pagination, filtering, sorting) within repository

Professional Tip

Avoid the Generic Repository trap that exposes universal CRUD methods (GetAll, GetById, etc.). Favor domain-specific repositories with explicit business methods like findOverdueInvoices() or getTopSellingProducts(). This makes code self-documenting and prevents abstraction leaks. For complex operations, don't hesitate to create combinable specification patterns.

Associated Tools and Frameworks

  • TypeORM: ORM with native Repository Pattern support for Node.js
  • Entity Framework (C#): built-in repository implementation with Unit of Work
  • Spring Data JPA: automatic repository generation via interfaces
  • Prisma: while being a query builder, integrates easily into repository pattern
  • MediatR: to combine repositories with CQRS and Mediator pattern
  • AutoMapper: facilitates mapping between domain entities and persistence models

The Repository Pattern represents an architectural investment that pays off long-term by drastically reducing technical debt. By isolating persistence, it allows teams to migrate to new storage technologies (from PostgreSQL to MongoDB for example) without rewriting business logic. For scalable projects, this pattern is essential for maintaining clean and testable architecture. The key to success lies in defining rich business interfaces rather than simple technical wrappers around an ORM.

Themoneyisalreadyonthetable.

In 1 hour, discover exactly how much you're losing and how to recover it.