Контроллеры(Controllers)

Контроллеры(Controllers) отвечают за обработку входящих запросов(Request) и возврат ответов(Response) клиенту.

HTTP-запрос от клиента к серверу

Назначение контроллера - получать и обрабатывать конкретные запросы к приложению. Механизм маршрутизации управляет, какой контроллер получает какие запросы. Часто каждый контроллер имеет более одного маршрута, и разные маршруты могут выполнять разные действия.

Чтобы создать базовый контроллер, мы используем классыarrow-up-right и декораторыarrow-up-right.

Декораторы связывают классы с необходимыми метаданными и позволяют Nest создавать карту маршрутизации (привязывать запросы к соответствующим контроллерам).

Маршрутизация

В следующем примере мы будем использовать декоратор @Controller(), который необходим для определения базового контроллера. Мы будем указывать необязательный префикс пути маршрута cats. Использование префикса пути в декораторе @Controller()позволяет нам легко группировать набор связанных маршрутов и минимизировать повторяющийся код.

circle-info

Чтобы создать контроллер с помощью интерфейса командной строки(Nest CLI), просто выполните команду $ nest g controller cats.

Декоратор @Get() перед методом findAll() указывает Nest создать обработчик для конкретной конечной точки для GET-запросов. Конечная точка соответствует методу HTTP-запроса (в данном случае GET) и пути маршрута.

Каков маршрут? Путь маршрута для обработчика определяется путем объединения (необязательного) префикса, объявленного для контроллера, и любого пути, указанного в декораторе запроса. Поскольку мы объявили префикс для каждого маршрута (cats) и не добавили никакой информации о пути в декоратор, Nest отобразит запросы GET / cats в этот обработчик. Как уже упоминалось, путь включает в себя как необязательный префикс пути контроллера, так и любую строку пути, объявленную в декораторе метода запроса.

В нашем примере выше, когда к этой конечной точке делается запрос GET, Nest направляет запрос в наш определенный пользователем метод findAll(). Обратите внимание, что имя метода, которое мы здесь выбираем, совершенно произвольно. Очевидно, что мы должны объявить метод для привязки маршрута, но Nest не придает никакого значения выбранному методу.

Этот метод возвращает код состояния 200 и связанный с ним ответ, который в данном случае является просто строкой. Почему это происходит? Для объяснения мы сначала представим концепцию, согласно которой Nest использует два различных варианта управления ответами:

Вариант управления ответами

Описание

Стандартный(рекомендуется)

Используя этот встроенный метод, когда обработчик запроса возвращает объект или массив JavaScript, он автоматически сериализуется в JSON. Однако, когда он возвращает тип примитива JavaScript (например, string, number, boolean), Nest отправит только значение, не пытаясь его сериализовать.

Это делает обработку ответов простой: просто возвращайте значение, а Nest позаботится обо всем остальном.

Кроме того, код состояния ответа по умолчанию всегда равен 200, за исключением запросов POST, которые используют 201. Мы можем легко изменить это поведение, добавив декоратор @HttpCode(...) на уровне обработчика (см. Коды состояния ниже).

Специфичный для библиотеки

Мы можем использовать зависящий от библиотеки (например, Express) объект ответа, который можно внедрить с помощью декоратора @Res() в сигнатуру обработчика метода (например, findAll(@Res()response)). При таком подходе у вас есть возможность (и ответственность) использовать собственные методы обработки ответов, предоставляемые этим объектом. Например, с помощью Express вы можете создавать ответы, используя код, подобный response.status(200).send()

circle-info

Вы не можете использовать оба подхода одновременно!

Nest определяет, когда обработчик использует @Res() или @Next(), указывая, что вы выбрали опцию для конкретной библиотеки. Если оба подхода используются одновременно, стандартный подход автоматически отключается для этого единственного маршрута и больше не будет работать должным образом.

Объект запроса

Обработчикам часто требуется доступ к деталям запроса клиента. Nest предоставляет доступ к объекту запроса базовой платформы (по умолчанию Express). Мы можем получить доступ к объекту запроса, указав Nest ввести его, добавив декоратор @Req()к сигнатуре обработчика.

circle-info

Чтобы воспользоваться преимуществами типизации Express(как в примере request: Requestвыше), установите пакет @types/express.

Объект запроса(Request object) представляет из себя HTTP-запрос и содержит строку запроса, параметры, HTTP-заголовки и тело(подробнее здесьarrow-up-right). В большинстве случаев нет необходимости получать эти свойства вручную. Вместо этого мы можем использовать декораторы, такие как @Body() или @Query(), которые доступны из коробки. Ниже приведен список предоставленных декораторов и объектов, которые они представляют.

Декоратор

Объект

@Request()

req

@Response(), @Res()*

res

@Next()

next

@Session()

req.session

@Param(key?: string)

req.params / req.params[key]

@Body(key?: string)

req.body / req.body[key]

@Query(key?: string)

req.query / req.query[key]

@Headers(name?: string)

req.headers / req.headers[name]

@Ip()

req.ip

*Для совместимости с типами базовых фреймворков(например, Express и Fastify) Nest предоставляет декораторы @Res() и @Response().@Res() - это просто псевдоним для @Response(). Оба непосредственно реализуют базовый нативный интерфейс объекта response платформы. При их использовании вы также должны импортировать типы для базовой библиотеки (например, @types/express), чтобы воспользоваться всеми преимуществами. Обратите внимание, что когда вы вводите либо @Res(), либо@Response()в обработчик метода, вы переводите Nest в специфичный для библиотеки режим для этого обработчика и становитесь ответственным за управление ответом. При этом вы должны выдать какой-то ответ, вызвав объект ответа (например, res.json(...) или res.send(...)), иначе HTTP-сервер зависнет.

Ресурсы

Ранее мы определили конечную точку для извлечения ресурса cats (GET). Обычно мы также хотим предоставить конечную точку, которая создает новые записи. Для этого давайте создадим обработчик с методом POST:

cats.controller.ts

Все очень просто. Nest предоставляет остальные стандартные декораторы конечных точек HTTP-запросов таким же образом - @Put(), @Delete(), @Patch(), @Options(), @Head() и @All(). Каждый из них представляет свой соответствующий метод HTTP-запроса.

Подстановочные знаки маршрута

Также поддерживаются маршруты на основе шаблонов. Например, звездочка используется в качестве подстановочного знака и будет соответствовать любой комбинации символов.

Путь маршрута 'ab*cd'будет соответствовать abcd, ab_cd, abecd и так далее. А символы ?, +, и () могут использоваться в пути маршрута и являются подмножествами их аналогов регулярных выражений. Дефис ( - ) и точка (.) интерпретируются буквально строковыми путями.

Статус коды

Как уже упоминалось, код состояния ответа всегда равен 200 по умолчанию, за исключением POST запросов, которые равны 201. Мы можем легко изменить это поведение, добавив @HttpCode(...) декоратор на уровне обработчика.

circle-info

ИмпортируйтеHttpCodeиз пакета@nestjs/common.

Часто ваш код состояния не статичен, а зависит от различных факторов. В этом случае вы можете использовать специфичный для библиотеки объект ответа (инжектируя @Res())(или, в случае ошибки, создать исключение).

Заголовки

Чтобы указать кастомный заголовок ответа, вы можете либо использовать декоратор @Header(), либо объект ответа для конкретной библиотеки (и вызвать res.header() напрямую).

circle-info

ИмпортируйтеHeaderиз пакета@nestjs/common.

Переадресация

Чтобы перенаправить ответ на определенный URL-адрес, вы можете либо использовать декоратор @Redirect(), либо объект ответа для конкретной библиотеки (и вызвать res.redirect() напрямую).

@Redirect() принимает обязательный аргумент url-адреса и необязательный аргумент кода состояния. Код состояния по умолчанию равен 302 (Found), если он опущен.

Иногда вам может потребоваться динамически определить код состояния HTTP или URL-адрес перенаправления. Сделайте это, вернув объект из метода обработчика маршрута:

Возвращаемые значения будут переопределять любые аргументы, переданные декоратору @Redirect(). Например:

Параметры маршрута

Маршруты со статическими путями не будут работать, когда вам нужно принять динамические данные как часть запроса (например, GET/cats/1, чтобы получить cat с id 1). Чтобы определить маршруты с параметрами, мы можем добавить маркеры параметров маршрута в путь маршрута, чтобы захватить динамическое значение в этой позиции в URL-адресе запроса. Маркер параметра маршрута в приведенном ниже примере декоратора @Get() демонстрирует это использование. Параметры маршрута, объявленные таким образом, могут быть доступны с помощью декоратора @Param(), который должен быть добавлен в сигнатуру метода.

@Param() используется для обертки параметра метода (params в приведенном выше примере) и делает параметры маршрута доступными в качестве свойств этого оформленного параметра метода внутри тела метода. Как видно из приведенного выше кода, мы можем получить доступ к параметру id по ссылке params.id. Вы также можете передать определенный маркер параметра декоратору, а затем ссылаться на параметр маршрута непосредственно по имени в теле метода.

circle-info

ИмпортируйтеParamиз пакета@nestjs/common.

Маршрутизация с под-доменами

Декоратор @Controller может использовать параметр host, чтобы требовать, чтобы http-хост входящих запросов соответствовал некоторому определенному значению.

circle-info

Поскольку Fastify не поддерживает вложенные маршруты, при использовании суб-доменной маршрутизации вместо них следует использовать экспресс-адаптер (по умолчанию).

Подобно пути path, параметр hosts может использовать токены для захвата динамического значения в этой позиции в имени хоста. Токен параметра хоста в приведенном ниже примере декоратора @Controller() демонстрирует это использование. Параметры хоста, объявленные таким образом, могут быть доступны с помощью декоратора @HostParam(), который должен быть добавлен в сигнатуру метода.

Для людей, работающих с разными языками программирования, может оказаться неожиданным узнать, что в Nest почти все разделяется между входящими запросами. У нас есть пул соединений с базой данных, singleton сервисы с глобальным состоянием и т.д. Помните, что Node.js не следует многопоточной модели состояния запроса/ответа, в которой каждый запрос обрабатывается отдельным потоком. Следовательно, использование singleton экземпляров полностью безопасно для наших приложений.

Однако существуют крайние случаи, когда время жизни контроллера на основе запросов может быть желаемым поведением, например кэширование по запросу в приложениях GraphQL, отслеживание запросов или мульти-tenancy. Узнайте, как управлять Scopes действия здесьarrow-up-right.

Асинхронность

Мы любим современный JavaScript и знаем, что извлечение данных в основном происходит асинхронно. Вот почему Nest поддерживает и хорошо работает с асинхронными функциями.

circle-info

Почитайте про async/await здесьarrow-up-right!

Каждая асинхронная функция должна возвращать Promise. Это означает, что вы можете вернуть отложенное значение, которое Nest сможет "зарезолвить" самостоятельно. Давайте рассмотрим пример этого:

cats.controller.ts

Приведенный выше код является полностью валидным. Кроме того, обработчики маршрутов Nest являются еще более мощными, поскольку они могут возвращать наблюдаемые потоки(observable streamsarrow-up-right) RxJS. Nest автоматически подпишется на источник внизу и примет последнее выброшенное значение (как только поток будет завершен).

Оба вышеперечисленных подхода работают, и вы можете использовать то, что соответствует вашим требованиям.

Полезная нагрузка в запросах

Наш предыдущий пример обработчика маршрута POST не принимал никаких клиентских параметров. Давайте исправим это, добавив сюда декоратор @Body().

Но сначала (если вы используете TypeScript) нам нужно определить схему DTOarrow-up-right (объекта передачи данных). DTO - это объект, который определяет, как данные будут передаваться по сети. Мы могли бы определить схему DTO с помощью интерфейсов TypeScript или с помощью простых классов. Интересно, что мы рекомендуем использовать классы здесь. Почему? Классы являются частью стандарта JavaScript ES6, и поэтому они сохраняются как реальные сущности в скомпилированном JavaScript. С другой стороны, поскольку интерфейсы TypeScript удаляются во время транспиляции, Nest не может ссылаться на них во время выполнения. Это важно, поскольку такие функции, как каналы(Pipes), предоставляют дополнительные возможности, когда они имеют доступ к метатипу переменной во время выполнения.

Давайте создадим класс CreateCatDto:

У него есть только три основных свойства. После этого мы можем использовать вновь созданный DTO внутри CatsController:

Обработка ошибок

Здесьarrow-up-right есть отдельная глава об обработке ошибок (т.е. работе с исключениями).

Пример

Ниже приведен пример, который использует несколько доступных декораторов для создания базового контроллера. Этот контроллер предоставляет несколько методов для доступа к внутренним данным и манипулирования ими.

cats.controller.ts

Поехали!

При полном определении вышеприведенного контроллера Nest все еще не знает, что CatsController существует, и в результате не создаст экземпляр этого класса.

Контроллеры всегда принадлежат модулю, поэтому мы включаем массив контроллеров в декоратор @Module(). Поскольку мы еще не определили никаких других модулей, кроме AppModule, мы будем использовать его для представления CatsController:

app.module.ts

Мы прикрепили метаданные к классу модуля с помощью декоратора @Module(), и теперь Nest может легко отражать, какие контроллеры должны быть смонтированы.

Приложение: Библиотечно-ориентированный подход

До сих пор мы обсуждали стандартный способ манипулирования ответами Nest. Второй способ манипулирования ответом - это использование специфичного для библиотеки объекта ответа. Для того чтобы ввести конкретный объект ответа, нам нужно использовать декоратор @Res(). Чтобы показать различия, давайте перепишем CatsController следующим образом:

Хотя этот подход работает и фактически позволяет обеспечить большую гибкость в некоторых отношениях, обеспечивая полный контроль над объектом ответа (манипуляция заголовками, библиотечными функциями и т.д.), его следует использовать с осторожностью. В целом же этот подход гораздо менее понятен и имеет некоторые недостатки. Основные недостатки заключаются в том, что вы теряете совместимость с функциями Nest, которые зависят от стандартной обработки ответов Nest, такими как перехватчики и декоратор @HttpCode(). Кроме того, ваш код может стать зависимым от платформы (поскольку базовые библиотеки могут иметь различные API для объекта ответа) и более трудным для тестирования (вам придется имитировать объект response и т.д.).

В результате всегда, когда это возможно, следует отдавать предпочтение стандартному подходу Nest.

Last updated