Что такое repository pattern в NestJS при работе с TypeORM?
Суть паттерна
Repository Pattern — это паттерн проектирования, который выносит всю логику доступа к данным в отдельный слой (репозиторий), скрывая от бизнес-логики детали того, как именно данные хранятся и извлекаются. Сервисы работают с сущностями через понятный объектно-ориентированный интерфейс, не зная о SQL-запросах, драйвере БД или структуре таблиц.
Как это реализовано в TypeORM
TypeORM предоставляет готовую реализацию паттерна — класс Repository<Entity>. Для каждой сущности можно получить репозиторий с методами find, findOne, save, delete, update и построителем запросов QueryBuilder.
Чтобы использовать репозиторий в NestJS, нужно:
- Зарегистрировать сущность в модуле через
TypeOrmModule.forFeature([User]). - Внедрить репозиторий в сервис через декоратор
@InjectRepository(User).
Пример базового использования
Смотри codeExamples — там показано подключение модуля, внедрение репозитория и базовые CRUD-операции.
Зачем нужен этот слой абстракции
- Разделение ответственности — сервис занимается бизнес-логикой, репозиторий отвечает только за доступ к данным.
- Тестируемость — в unit-тестах репозиторий легко подменить моком, не поднимая реальную БД.
- Единая точка сложных запросов — специфичные выборки, джойны, фильтрацию удобно собирать в одном месте, а не размазывать по сервисам.
- Гибкость — теоретически источник данных можно заменить (другая БД, внешний API), не переписывая бизнес-логику, если интерфейс репозитория сохранён.
Кастомные репозитории
Когда стандартных методов Repository не хватает, в NestJS принято создавать кастомный провайдер поверх репозитория — отдельный класс, который инжектит Repository<Entity> и добавляет собственные методы (например, findActiveUsers или findByEmailWithRoles). Начиная с TypeORM 0.3.x подход extends Repository через @EntityRepository устарел, вместо этого используют обычный сервис-обёртку или Repository.extend.
Отличие от простого использования Repository напрямую в контроллере
Хотя технически можно инжектировать Repository прямо в контроллер, хорошей практикой считается делать это только внутри сервиса. Так контроллер остаётся тонким и отвечает только за HTTP-слой, а вся работа с данными и бизнес-правила инкапсулированы в сервисе поверх репозитория.
Итог
Repository Pattern в связке NestJS и TypeORM — это стандартный слой между бизнес-логикой и базой данных, реализованный через Repository<Entity>, который подключается через модуль (forFeature) и внедряется через Dependency Injection (@InjectRepository). Он упрощает тестирование, разделяет ответственность и даёт единое место для запросов к конкретной сущности.
Что хочет услышать интервьюер
Кандидат объясняет, что Repository Pattern отделяет доступ к данным от бизнес-логики
Знает, как подключить сущность через TypeOrmModule.forFeature и внедрить репозиторий через @InjectRepository
Понимает практическую пользу: упрощение тестирования, единая точка для запросов
Может привести пример кастомного репозитория для сложных запросов
Понимает, что репозиторий обычно используется внутри сервиса, а не напрямую в контроллере
Пример: Регистрация сущности и внедрение репозитория
// users.module.ts
@Module({
imports: [TypeOrmModule.forFeature([User])], // регистрируем сущность в модуле
providers: [UsersService],
exports: [UsersService],
})
export class UsersModule {}
// users.service.ts
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User)
private readonly usersRepository: Repository<User>, // репозиторий внедряется через DI
) {}
findAll(): Promise<User[]> {
return this.usersRepository.find();
}
findOne(id: number): Promise<User | null> {
return this.usersRepository.findOneBy({ id });
}
create(dto: CreateUserDto): Promise<User> {
const user = this.usersRepository.create(dto);
return this.usersRepository.save(user);
}
}
Пример: Кастомный репозиторий со специфичным запросом
// users.repository.ts
@Injectable()
export class UsersRepository {
constructor(
@InjectRepository(User)
private readonly repo: Repository<User>,
) {}
// специфичный запрос, который не хочется размазывать по сервисам
findActiveUsers(): Promise<User[]> {
return this.repo
.createQueryBuilder('user')
.where('user.isActive = :isActive', { isActive: true })
.getMany();
}
}
Типичные ошибки
Путают Repository Pattern с самим ORM, не понимая, что это паттерн проектирования, а TypeORM — его конкретная реализация
Инжектируют репозиторий прямо в контроллер, минуя сервисный слой
Не знают, что нужно зарегистрировать сущность через TypeOrmModule.forFeature перед использованием @InjectRepository
Используют устаревший декоратор @EntityRepository, не зная, что он deprecated в новых версиях TypeORM
Не могут объяснить, зачем нужен этот слой абстракции, если можно просто писать SQL-запросы напрямую


