Оглавление:
Карта сайта:
Оглавление:
Карта сайта:
Контроллеры отвечают за обработку входящих запросов и возврат ответов клиенту
Цель контроллера — принимать определенные запросы для приложения. Механизм маршрутизации управлеяет, какой из контроллеров получает, какие запросы. Зачастую каждый контроллер имеет более одного маршрута, и разные маршруты могут выполнять разные действия.
Для создания контроллера мы используем классы и декораторы. Декораторы связывают классы с необходимыми метаданными и позволяют Nest создавать карту маршрутизации (связывая запросы с соответствующими контроллерами).
Подсказка Для быстрого создания CRUD контроллера со встроенной валидацией, вы можете использовать CRUD-генератор CLI:nest g resource [name].
В следующем примере мы будем использовать декоратор @Controller(), который требуется для определения базового контроллера. Мы укажем необязательный префикс пути маршрута cats(кошки). Использование префикса пути в декораторе @Controller() позволяет нам легко группировать набор связанных маршрутов, и свестви к минимуму повторяющийся код. Например, мы можем захотеть сгруппировать набор маршрутов, управляющих взаимодействием с сущностью клиента по маршруту /customers(клиенты). В этом случае, мы могли бы указать префикс пути customers в декораторе @Controller(), так чтобы не повторять эту часть пути для каждого маршрута в файле.
cats.controller.ts
import { Controller, Get } from '@nestjs/common'; @Controller('cats') export class CatsController { @Get() findAll(): string { return 'Этот action вернет всех кошек'; } }
Подсказка Чтобы создать контроллер с помощью CLI, просто выполните команду$ nest g controller cats.
Декортор @Get() перед методом findAll() указывает Nest создать обработчик для определенной конечной точки (endpoint) для Http-запроса. Конечная точка соответствует методу HTTP-запроса (в данном случае GET) и пути маршрута. Что такое путь маршрута? Путь маршрута для обработчика определяется объединением (необязательного) префикса, объявленного для контроллера, и пути указанного в декораторе метода. Посколько мы объявили префикс для каждого маршрута (cats), и не добавил информации о пути в декораторе, Nest свяжет запросы GET /cats с этим обработчиком. Как уже упоминалось, путь включает в себя как необязательный префикс пути контроллера, так и любую строку пути, объявленную в декораторе метода запроса. Например, префикс пути customers вместе с декоратором @Get('profile') создают маршрут для таких запросов, как GET /customers/profile.
В нашем примере выше, когда GET-запрос попадает на endpoint, Nest отправляет запрос в наш метод findAll(). Обратите внимание, что имя метода, которое мы выбираем здесь, совершенно произвольно. Название метода может быть любым.
Этот метод вернет код состояния 200 и связанный с ним ответ, который в данном случае является просто строкой. Почему так происходит? Чтобы объяснить, мы сначала представим концепцию, согласно которой Nest использует два различных варианта для обработки ответов:
| Стандартый (рекоммендуемый) | Используя этот встроенный метод, когда обработчик запроса возвращает объект JavaScript или массив, он будет автоматически сериализован в JSON. Однако если он возвращает примитивный тип JavaScript (например, string, number, boolean), Nest отправит только значение, не пытаясь его сериализовать. «Это упрощает обработку ответов»: просто верните значение, а Nest позаботится обо всем остальном.Кроме того, код состояния ответа по умолчанию всегда равен 200, за исключением POST-запросов, которые используют 201 код. Мы можем легко изменить это поведение, добавив декоратор @HttpCode(…) на уровне обработчика (см. Коды состояния). |
| Специфичный для библиотеки | Мы можем использовать зависящий от библиотеки (например Express) объект ответа, который может быть внедрен с помощью декоратора @Res() в сигнатуру обработчика метода (например, findAll(@Res() response)). При таком подходе у вас есть возможность использовать собственные методы обработки ответов, предоставляемые этим объектом. Например, с помощью Express вы можете создавать ответы, используя такой код, как response.status(200).send(). |
Предупреждение Вы не можете использовать оба подхода одновременно!
Nest определяет, когда обработчик использует@Res()или@Next(), что указывает на то, что вы выбрали варинт для определенной библиотеки. Если оба подхода используются одновременно, Стандартый подход автоматически отключается для этого единственного маршрута и больше не будует работать должным образом. Чтобы использовать оба подхода одновременно (например, by injecting the response object to only set cookies/headers but still leave the rest to the framework), вы должны установить для параметраpassthroughзначениеtrueв декораторе@Res({ passthrough: true }).
Обработчикам часто требуется доступ к информации из запроса клиента. Nest предоставляет доступ к объекту request базовой платформы (по умолчанию Express). Мы можем получить доступ к объекту request, указав Nest внедрить его, добавив декоратор @Req() в сигнатуру обработчика.
cats.controller.ts
import { Controller, Get, Req } from '@nestjs/common'; import { Request } from 'express'; @Controller('cats') export class CatsController { @Get() findAll(@Req() request: Request): string { return 'This action returns all cats'; } }
Подсказка Чтобы воспользоваться преимущество типизации typescript дляexpress(как с параметромrequest: Requestв примере выше), для этого установите пакет@types/express.
Объект request представляет HTTP запрос и содержит свойства для доступа к параметрам строки запроса, параметрам пути, HTTP заголовкам (headers), и телу запроса (body) (подробнее здесь). В большинстве случаев нет необходимости получать эти свойства вручную. Вместо этого мы можем использовать специальные декораторы, такие как @Body() или @Query(), которые доступны из коробки. Ниже представлен список декораторов и простых объектов, определенных для платформы, которую они представляют.
@Request(), @Req() | 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 |
@HostParam() | req.hosts |
* Для совместимости с типизацией на базовых платформах HTTP (например, Express и Fastify) Nest предоставляет декораторы @Res() и @Response(). @Res() — это просто псевдоним для @Response(). Обе библиотеки предоставляют собственный интерфейс для ответа. При их использовании вы также должны импортировать типизацию для базовой библиотеки (например, @types/express), чтобы воспользоваться всеми преимуществами. Обратите внимание, что когда вы пишите @Res() или @Response(), вы переводите Nest в режим использования сторонней библиотеки для этого обработчика и берете на себя ответственность за управление ответом. При этом вы должны выдать какой-либо ответ, выполнив вызов объекта ответа (например, res.json(…) или res.send(…)), иначе HTTP-сервер зависнет.
совет Чтобы узнать, как создавать свои собственные декораторы, посетите главу custom-decorators.
Ранее мы определили конечную точку для получения ресурса cats (GET route). Для создания новой записи создадим обработчик POST:
cats.controller.ts
import { Controller, Get, Post } from '@nestjs/common'; @Controller('cats') export class CatsController { @Post() create(): string { return 'This action adds a new cat'; } @Get() findAll(): string { return 'This action returns all cats'; } }
Это так просто. Nest предоставляет декораторы для всех стандартных HTTP-методов: @Get(), @Post(), @Put(), @Delete(), @ Patch(), @Options() и @Head(). Кроме того, @All() определяет конечную точку, которая обрабатывает их все.
Также поддерживаются маршруты на основе шаблонов. Например, звездочка используется в качестве подстановочного знака и будет соответствовать любой комбинации символов.
@Get('ab*cd') findAll() { return 'This route uses a wildcard'; }
Путь маршрута 'ab*cd' будет соответствовать abcd, ab_cd, abecd и так далее. Символы ?, +, * и () могут использоваться в пути маршрута и являются подмножествами их аналогов регулярных выражений. Дефис (-) и точка (.) интерпретируются буквально строковыми путями.
Как уже упоминалось, ответ status по умолчанию всегда равен 200, за исключением запросов POST, которые имеют значение 201. Мы можем легко изменить это поведение, добавив декоратор @HttpCode(…) на уровне обработчика.
@Post() @HttpCode(204) create() { return 'This action adds a new cat'; }
Совет ИмпортируйтеHttpCodeиз пакета@nestjs/common.
Часто ваш код состояния не является статичным, а зависит от различных факторов. В этом случае вы можете использовать специфичный для библиотеки ответ (внедрить с помощью объекта @Res()) (или, в случае ошибки, создать исключение).
Чтобы указать собственный заголовок ответа, вы можете использовать декоратор @Header() или объект ответа для конкретной библиотеки (и напрямую вызывать res.header()).
@Post() @Header('Cache-Control', 'none') create() { return 'This action adds a new cat'; }
Совет Импортируйте заголовок из пакета @nestjs/common.
Чтобы перенаправить ответ на определенный URL-адрес, вы можете использовать декоратор @Redirect() или объект ответа для конкретной библиотеки (и напрямую вызывать res.redirect()).
@Redirect() принимает два аргумента, url и statusCode, оба являются необязательными. Значение по умолчанию для statusCode равно 302 (Найдено), если оно не указано.
@Get() @Redirect('https://nestjs.com', 301)
Иногда может потребоваться динамическое определение кода состояния HTTP или URL-адреса перенаправления. Сделайте это, вернув объект из метода обработчика маршрута с формой:
{ "url": string, "statusCode": number }
Возвращаемые значения переопределяют любые аргументы, переданные декоратору @Redirect(). Например:
@Get('docs') @Redirect('https://nestjs.ru', 302) getDocs(@Query('version') version) { if (version && version === '5') { return { url: 'https://nestjs.ru/v5/' }; } }
Маршруты со статическими путями не будут работать, если вам нужно принять динамические данные как часть запроса (например, «GET /cats/1», чтобы получить cat с идентификатором «1»). Чтобы определить маршруты с параметрами, мы можем добавить параметр маршрута token в путь маршрута, чтобы зафиксировать динамическое значение в этой позиции в URL-адресе запроса. Доступ к параметрам маршрута, объявленным таким образом, можно получить с помощью декоратора @Param(), который следует добавить в сигнатуру метода.
@Get(':id') findOne(@Param() params): string { console.log(params.id); return `This action returns a #${params.id} cat`; }
@Param() используется для декорирования параметра метода (params в приведенном выше примере) и делает параметры route доступными как свойства этого декорированного параметра метода внутри тела метода. Как видно из приведенного выше кода, мы можем получить доступ к параметру id, обратившись к params.id. Вы также можете передать конкретный токен параметра в декоратор, а затем напрямую ссылаться на параметр маршрута по имени в теле метода.
Hint ImportParamfrom the@nestjs/commonpackage.
@Get(':id') findOne(@Param('id') id: string): string { return `This action returns a #${id} cat`; }
Декоратор @Controller может использовать опцию host, чтобы входящие запросы соответствовал определенному значению.
@Controller({ host: 'admin.example.com' }) export class AdminController { @Get() index(): string { return 'Admin page'; } }
Warning Поскольку Fastify не поддерживает вложенные маршрутизаторы, при использовании маршрутизации поддоменов вместо этого следует использовать адаптер Express (по умолчанию).
Подобно маршруту «path», параметр «hosts» может использовать токены для захвата динамического значения. Доступ к объявленным таким образом параметрам хоста можно получить с помощью декоратора @HostParam(), который следует добавить в сигнатуру метода.
@Controller({ host: ':account.example.com' }) export class AccountController { @Get() getInfo(@HostParam('account') account: string) { return account; } }
Для людей, использующих разные языки программирования, может оказаться неожиданным узнать, что в Nest почти все делится между входящими запросами. У нас есть пул соединений с базой данных, одноэлементные сервисы с глобальным состоянием и т. д. Помните, что Node.js не следует многопоточной модели запроса/ответа без сохранения состояния, в которой каждый запрос обрабатывается отдельным потоком. Следовательно, использование экземпляров singleton полностью безопасно для наших приложений.
Однако бывают крайние случаи, когда желаемым поведением может быть время жизни контроллера на основе запроса, например, кэширование каждого запроса в приложениях GraphQL или отслеживание запросов.
Нам нравится современный JavaScript, и мы знаем, что извлечение данных в основном асинхронно. Вот почему Nest поддерживает и хорошо работает с «асинхронными» функциями.
Каждая асинхронная функция должна возвращать «Promise». Это означает, что вы можете вернуть отложенное значение, которое Nest сможет разрешить самостоятельно. Давайте посмотрим на пример этого:
cats.controller.ts
@Get() async findAll(): Promise<any[]> { return []; }
The above code is fully valid. Furthermore, Nest route handlers are even more powerful by being able to return RxJS observable streams. Nest will automatically subscribe to the source underneath and take the last emitted value (once the stream is completed).
Приведенный выше код полностью действителен. Кроме того, обработчики маршрутов Nest еще более эффективны, поскольку могут возвращать RxJS наблюдаемые потоки. Nest автоматически подпишется на источник внизу и возьмет последнее значение (после завершения потока).
cats.controller.ts
@Get() findAll(): Observable<any[]> { return of([]); }
Оба вышеперечисленных подхода работают, и вы можете использовать все, что соответствует вашим требованиям.
Наш предыдущий пример обработчика маршрута POST не принимал никаких клиентских параметров. Давайте исправим это, добавив сюда декоратор @Body().
Но сначала (если вы используете TypeScript) нам нужно определить схему DTO (объект передачи данных). DTO — это объект, который определяет способ отправки данных по сети. Мы могли бы определить схему DTO, используя интерфейсы TypeScript или простые классы. Интересно, что здесь мы рекомендуем использовать классы. Почему? Классы являются частью стандарта JavaScript ES6, поэтому они сохраняются как реальные объекты в скомпилированном JavaScript. С другой стороны, поскольку интерфейсы TypeScript удаляются во время транспиляции, Nest не может ссылаться на них во время выполнения. Это важно, потому что такие функции, как Pipes, открывают дополнительные возможности, когда у них есть доступ к метатипу переменной во время выполнения.
Создадим класс CreateCatDto:
create-cat.dto.ts
export class CreateCatDto { name: string; age: number; breed: string; }
У него всего три основных свойства. После этого мы можем использовать только что созданный DTO внутри CatsController:
cats.controller.ts
@Post() async create(@Body() createCatDto: CreateCatDto) { return 'This action adds a new cat'; }
Совет НашValidationPipeможет отфильтровывать свойства, которые не должны быть получены обработчиком метода. В этом случае мы можем внести допустимые свойства в белый список, и любое свойство, не включенное в белый список, автоматически удаляется из результирующего объекта. В примере «CreateCatDto» нашим белым списком являются свойства «имя», «возраст» и «дети».
Ниже приведен пример использования нескольких доступных декораторов для создания базового контроллера. Этот контроллер предоставляет несколько методов для доступа к внутренним данным и управления ими.
cats.controller.ts
import { Controller, Get, Query, Post, Body, Put, Param, Delete } from '@nestjs/common'; import { CreateCatDto, UpdateCatDto, ListAllEntities } from './dto'; @Controller('cats') export class CatsController { @Post() create(@Body() createCatDto: CreateCatDto) { return 'This action adds a new cat'; } @Get() findAll(@Query() query: ListAllEntities) { return `This action returns all cats (limit: ${query.limit} items)`; } @Get(':id') findOne(@Param('id') id: string) { return `This action returns a #${id} cat`; } @Put(':id') update(@Param('id') id: string, @Body() updateCatDto: UpdateCatDto) { return `This action updates a #${id} cat`; } @Delete(':id') remove(@Param('id') id: string) { return `This action removes a #${id} cat`; } }
Свойство Nest CLI предоставляет генератор (схему), который автоматически генерирует весь шаблонный код, чтобы помочь нам избежать всего этого и упростить работу разработчика. Подробнее об этой функции читайте
Когда вышеуказанный контроллер полностью определен, Nest по-прежнему не знает о существовании «CatsController» и, как следствие, не будет создавать экземпляр этого класса.
Контроллеры всегда принадлежат модулю, поэтому мы включаем массив controllers в декоратор @Module(). Поскольку мы еще не определили какие-либо другие модули, кроме корневого «AppModule», мы будем использовать его для представления «CatsController»:
app.module.ts
import { Module } from '@nestjs/common'; import { CatsController } from './cats/cats.controller'; @Module({ controllers: [CatsController], }) export class AppModule {}
Мы прикрепили метаданные к классу модуля с помощью декоратора @Module(), и теперь Nest может легко определить, какие контроллеры необходимо смонтировать.
До сих пор мы обсуждали стандартный способ управления ответами Nest. Второй способ манипулирования ответом — использование специфичного для библиотеки объекта ответа. Чтобы внедрить конкретный объект ответа, нам нужно использовать декоратор @Res(). Чтобы показать различия, давайте перепишем CatsController следующим образом:
import { Controller, Get, Post, Res, HttpStatus } from '@nestjs/common'; import { Response } from 'express'; @Controller('cats') export class CatsController { @Post() create(@Res() res: Response) { res.status(HttpStatus.CREATED).send(); } @Get() findAll(@Res() res: Response) { res.status(HttpStatus.OK).json([]); } }
Хотя этот подход работает и на самом деле обеспечивает большую гибкость в некоторых отношениях, предоставляя полный контроль над объектом ответа (манипулирование заголовками, специфичные для библиотеки функции и т. д.), его следует использовать с осторожностью. В целом подход гораздо менее ясен и имеет некоторые недостатки. Основным недостатком является то, что ваш код становится зависимым от платформы (поскольку базовые библиотеки могут иметь разные API-интерфейсы для объекта ответа) и его сложнее тестировать (вам придется имитировать объект ответа и т. д.).
Кроме того, в приведенном выше примере вы теряете совместимость с функциями Nest, которые зависят от стандартной обработки ответов Nest, такими как перехватчики и декораторы @HttpCode() / @Header(). Чтобы исправить это, вы можете установить для параметра «passthrough» значение «true» следующим образом:
@Get() findAll(@Res({ passthrough: true }) res: Response) { res.status(HttpStatus.OK); return []; }
Теперь вы можете взаимодействовать с нативным объектом ответа (например, устанавливать куки или заголовки в зависимости от определенных условий), но остальное оставьте фреймворку.