Пример UsersController
Возьмём пример из feeldown-backend (ссылка вне зоны доступа).
UsersController – классический контроллер в NestJS. Он занимается только
маршрутизацией, а всю бизнес-логику делегирует сервису. Это идеальное
попадание в Single Responsibility Principle.
Код контроллера
@Controller(ROUTE)
@UseGuards(AuthGuard)
export class UsersController {
public constructor(private readonly service: Service) {}
@Get(ROUTES.GET_ONE)
@Public()
public getOne(
@UserFindOptions(Parameters.slug) options: ResolvedUsernameSlug,
) {
return this.service.getOne(options);
}
@Update([ROUTES.PUT, ROUTES.PATCH])
@OnlyMe(Parameters.slug, "slug")
public put(
@UserFindOptions(Parameters.slug) options: ResolvedUsernameSlug,
@Body() data: UserUpdateDto,
) {
return this.service.update(options, data);
}
@Delete(ROUTES.DELETE)
@OnlyMe(Parameters.id, "id")
public delete(@Param(Parameters.id) id: string) {
return this.service.delete({ id });
}
}Что хорошо
1. Чёткое разделение ответственности
- Контроллер не знает, как работает
UserService– он просто вызывает его методы. - Вся бизнес-логика (поиск пользователя, обновление, удаление) вынесена в
UsersService. - Контроллер отвечает только за:
- Приём запросов,
- Валидацию входных данных (через DTO и декораторы),
- Вызов соответствующих методов сервиса,
- Возврат ответа.
2. Использование кастомного декоратора @UserFindOptions
Вместо того чтобы вручную парсить slug и определять, является ли он @me,
контроллер использует декоратор:
@UserFindOptions(Parameters.slug) options: ResolvedUsernameSlugДекоратор:
- Извлекает значение параметра из
request.params, - Передаёт его в
UserFindOptionsPipe, - Который, в свою очередь, использует
SlugPipeиServerUserServiceдля получения готового объектаoptions.
Преимущества:
- Контроллер не знает о существовании
SlugPipeиServerUserService. - Логика трансформации вынесена в отдельный слой (декоратор + пайп).
- При изменении правил поиска пользователя (например, добавить поддержку email) правим только пайп – контроллер не трогаем.
3. Отсутствие дублирования
Раньше в каждом методе приходилось писать:
const where = SlugPipe.resolveMe(slug, me);Теперь это делается один раз в декораторе. Все методы используют единообразный подход.
4. Соблюдение Dependency Injection
В декораторе UserFindOptions используются пайпы, которые внедряются через
DI-контейнер NestJS:
@Injectable()
export class UserFindOptionsPipe implements PipeTransform {
public constructor(
private readonly slugPipe: UsernameSlugPipe,
private readonly service: ServerUserService,
) {}
// ...
}Нет ни одного вызова new – всё создаётся и управляется фреймворком.
Итог
UsersController – идеальный пример того, как должен выглядеть контроллер:
- Одна ответственность – маршрутизация.
- Минимум зависимостей – только сервис и декораторы.
- Вся сложность вынесена – трансформация параметров, бизнес-логика, проверка прав – всё в других слоях.
Такой подход делает код:
- Читаемым – легко понять, какие эндпоинты есть и что они делают.
- Тестируемым – контроллер можно протестировать, подставив мок-сервис.
- Гибким – изменения в логике поиска пользователя не затрагивают контроллер.
Запомните: Контроллер должен быть «тонким» – его задача только принимать запросы и вызывать сервисы. Вся остальная логика – это ответственность других классов.