Skip to Content

Пример 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 – идеальный пример того, как должен выглядеть контроллер:

  • Одна ответственность – маршрутизация.
  • Минимум зависимостей – только сервис и декораторы.
  • Вся сложность вынесена – трансформация параметров, бизнес-логика, проверка прав – всё в других слоях.

Такой подход делает код:

  • Читаемым – легко понять, какие эндпоинты есть и что они делают.
  • Тестируемым – контроллер можно протестировать, подставив мок-сервис.
  • Гибким – изменения в логике поиска пользователя не затрагивают контроллер.

Запомните: Контроллер должен быть «тонким» – его задача только принимать запросы и вызывать сервисы. Вся остальная логика – это ответственность других классов.

Last updated on