Skip to content

NestJS Repositories

Repositories hide how data is stored so services express business rules without knowing TypeORM, Prisma, or Mongoose details. This lesson shows a practical NestJS repository setup.

Why Use Repositories?

A repository is an injectable provider that exposes domain-friendly methods (findByEmail, save, deleteById) while encapsulating vendor-specific query APIs. Services depend on the repository interface, which makes unit tests and ORM swaps much simpler.

export abstract class UsersRepository {
  abstract findByEmail(email: string): Promise<User | null>;
  abstract save(user: User): Promise<User>;
}

@Injectable()
export class TypeOrmUsersRepository extends UsersRepository {
  constructor(@InjectRepository(User) private repo: Repository<User>) {
    super();
  }

  findByEmail(email: string) {
    return this.repo.findOne({ where: { email } });
  }

  save(user: User) {
    return this.repo.save(user);
  }
}

Services inject UsersRepository (the abstract token). In tests you provide an in-memory stub; in production you provide the TypeORM implementation.

Binding Interface to Implementation

{
  provide: UsersRepository,
  useClass: TypeOrmUsersRepository,
}
  • Register the abstract class as the token and the concrete class as useClass.
  • Keep repository methods named after domain intent, not ORM verbs alone.
  • Return domain entities/DTOs from repositories, not framework response objects.
  • Put cross-entity transaction orchestration in an application service, not every repository.

Repository Pattern Cheatsheet

Roles of each layer when using repositories.

Layer Responsibility
Controller HTTP mapping only
Service Business rules and orchestration
Repository Persistence reads/writes
ORM client Low-level query execution

Testing With Fake Repositories

Because services depend on an abstract repository, unit tests can inject a Map-based fake without spinning up a database. That keeps service tests fast and focused on rules.

When a Thin Pass-Through Is Enough

For tiny CRUD apps, injecting Prisma/TypeORM directly into services can be acceptable. Introduce an explicit repository when you have repeated query shapes, multiple data sources, or heavy testing needs.

Common Mistakes

  • Creating a repository that merely renames every ORM method without adding value.
  • Leaking TypeORM QueryBuilder objects back to controllers.
  • Putting authorization decisions inside repositories instead of services/guards.
  • One giant "god repository" for every table in the application.

Key Takeaways

  • Repositories isolate persistence technology behind domain-friendly APIs.
  • Bind abstract repository tokens to concrete ORM implementations in modules.
  • Services become easier to unit test with fake repositories.
  • Prefer repositories when query patterns and tests justify the extra layer.

Pro Tip

Name repository methods after use cases (findActiveSubscribers) rather than SQL shape. That namesake becomes documentation for why the query exists.