NestJS / 38 минут
Контроллеры и сервисы
Разделяйте HTTP-представление и сценарии приложения.
- Разделяйте HTTP-представление и сценарии приложения.
- Применить тему в задаче «Контроллеры и сервисы» и объяснить граничные случаи.
Около 8 минут на чтение и разбор примеров. Практика — отдельно, в своём темпе.
HTTP как внешний контракт
Контроллер связывает HTTP-запрос с возможностью приложения. Его контракт включает метод, путь, параметры, тело, статус и форму ответа. Controller задаёт общий префикс, Get или Post — конкретный маршрут. Nest регистрирует методы при сборке приложения; обычный вызов метода класса сам по себе не отправляет HTTP-запрос и не запускает guards или pipes.
@Controller('books')
class BooksController {
@Get('status')
status() { return { available: true }; }
}Пример обслуживает GET /books/status. Контроллер нужно указать в controllers модуля. Префикс приложения, если он задан через setGlobalPrefix, добавляется отдельно. При проверке пути учитывайте все уровни: префикс, Controller и метод. Это помогает понять 404 без случайного изменения бизнес-логики.
Параметры и границы ввода
Param читает именованный параметр маршрута, Query — строку запроса, Body — разобранное тело. Декоратор выбирает источник данных, но тип TypeScript не доказывает правильность внешнего значения. id: number без pipe не превращает строку пути в число. DTO с ValidationPipe рассматриваются в следующих главах.
@Controller('books')
class LookupController {
@Get(':id')
find(@Param('id') id: string, @Query('lang') lang?: string) {
return { id, lang: lang ?? 'ru' };
}
}Параметры query могут отсутствовать, повторяться или иметь неожиданную форму. Прежде чем передать их в поиск или сортировку, задайте допустимые значения и границы. Не превращайте произвольное имя из query в имя поля БД. Обработчик должен передавать сценарию уже понятный вход, а не перекладывать валидацию на случайное поведение хранилища.
Сценарий принадлежит сервису
Контроллер остаётся небольшим: принимает проверенные значения, вызывает сервис, формирует внешний ответ. Сервис выполняет сценарий и управляет нужными зависимостями. Такой код проще проверять без HTTP, повторно использовать в очереди и изменять при смене транспорта. Сервис не должен зависеть от response Express ради отправки статуса.
@Injectable()
class TitlesService {
find(id: string) { return { id, title: 'JavaScript' }; }
}
@Controller('titles')
class TitlesController {
constructor(private readonly titles: TitlesService) {}
@Get(':id')
find(@Param('id') id: string) { return this.titles.find(id); }
}В модуле зарегистрируйте TitlesService как provider, а TitlesController как controller. Если сервис расположен в другом модуле, используйте imports и exports вместо создания второго экземпляра в контроллере. В маленькой задаче closure может передать учебные данные, но реальное хранилище принадлежит адаптеру, а не файлу HTTP-маршрутов.
Ответ и статус
При стандартной обработке Nest сериализует возвращённое значение. Можно вернуть Promise: framework дождётся результата. Для GET успешный статус обычно 200, для POST по умолчанию 201. Если контракт требует другого, используйте HttpCode явно. Отсутствующий ресурс оформляйте соответствующим исключением, а не успешным ответом с текстом «ошибка».
@Post()
@HttpCode(201)
create(@Body() input: CreateBookDto) {
return this.books.create(input);
}Здесь books и CreateBookDto предполагаются частью соответствующей feature. Если внедрить нативный Res и самостоятельно отправлять ответ, меняется стандартный путь обработки. Это полезно для отдельных случаев, но легко мешает преобразованию ответа interceptors. Не смешивайте return объекта и ручной res.send без понимания режима passthrough и ответственности за завершение.
Публичные поля выбирают явно
Запись аккаунта в БД содержит больше, чем разрешено клиенту: парольный хеш, служебные отметки, настройки восстановления. Возврат всего объекта сервиса может случайно раскрыть их после изменения схемы. Постройте публичный DTO с явным списком полей. Удаление только password не защищает от появления нового приватного поля завтра.
Для списка пользователей и личного профиля могут требоваться разные ответы. Тип возвращаемого значения TypeScript помогает разработчику, но не вырезает лишние свойства во время исполнения. Если применяете сериализацию декораторами, проверьте реальное поведение на HTTP-ответе и зарегистрируйте нужный interceptor. Простая явная проекция часто понятнее сложной магии.
Практика публичного маршрута
Создайте настоящий GET /public. Из переданного user верните только id и email, исключив passwordHash. В solve соберите приложение, вызовите init, отправьте запрос через Supertest к getHttpServer и верните body. finally с app.close нужен даже при ошибке проверки. Постоянный порт в учебной среде открывать не требуется.
Проверьте не только правильные значения, но и отсутствие приватного свойства. В итоговой библиотеке разделите контроллеры книг и аккаунтов, держите операции с БД в сервисах и репозиториях. Затем добавьте проверки 404, неверного параметра и запрета доступа. Они подтверждают HTTP-контракт, который невозможно проверить одним прямым вызовом метода сервиса.