nest new
- позволяет создать новый сервис, API или любое другое приложение и дополнительные опции. Если не передать name
, то СLI спросит какое имя у проекта.
Команда nest new <name> [options]
- создаёт новый проект с именем <name>
. Опции при этом могут быть следующими:
- --dry-run - создать проект без реальных изменений.
- --skip-git - создать проект без ининциализации git репозитория.
- --skip-install - создать проект без установки зависимостей.
- --package-manager - выбрать менеджер пакетов (может быть или npm или yarn).
- --language - выбрать язык программирования, который будет использоватся в проекте (может быть или TS или JS).
nest generate
- позволяет создать новый компонент к уже существующему репозиторию по некоторой схеме. Схема это например контроллер, сервис или т.п., с некоторым именем.
Команда nest generate <schematic> <name> [options]
- создаёт компонент по схеме c именем :
- --dry-run - создать компонент без реальных изменений.
- --project - выбор проекта для monorepo.
- --flat - создаёт компонент без вложенной папки.
- --spec - создаёт файл тестов.
- --no-spec - не создавать файл тестов.
nest build
- позволяет собрать проект для production
мода.
Команда nest build
- собирает production вариант приложения, с некоторыми опциями:
- --path - путь до tsconfig.json если он кастомизирован, по умолчанию он будет лежать в корне проекта.
- --config - путь до nest-cli.json, аналогично с tsconfig.json.
- --watch - пересобирать проект при появлении каких-либо изменений.
- --webpack - использовать webpack для сборки.
- --webpackPath - указать путь до webpack.
- --tsc - собрать проект с помощью tsc.
nest start
- использует те же опции, что и nest build
, но и ещё с дополнительными опциями.
Команда nest start
имеет допольнительные, следующие, опции:
- --preserveWatchOutput - не очищать консоль при сборке.
- --watchAssets - следит не только за ts файлами.
- --debug - режим debug, полезен при отладке приложения.
- --exec - путь до bin файла в node.
Модули - это по сути строительные блоки из которых состоит приложение. В обязательном у каждого приложения на Nest.js есть хотя бы один модуль - корневой модуль, который часто называется appModule
. Рекомендуется разделять модули по доменным областям.
В маленьких микросервисах достаточно одного модуля, в больших же сервисах и монолитах может быть вложенность три. В реальности вложенность больше трёх редко бывает. Модули поддерживают чистоту приложения, её отделимость и возможность в любой момент отрефакторить и вынести что-то в отдельным микросервис.
Суммарно зависимости, которые скрывает за собой модуль это:
- импорты. Это другие модули, которые текущий модуль импортирует в себя.
- контроллеры. Это http или другие протоколы контроллеры, которые позволяет взаемодействвоать с приложением.
- провайдеры. Это различные сервисы, репозитории баз данных и другие провайдеры, которые могут быть задаными.
- экспортс. Это то, что будет экспортировано. Это могут быть или провайдеры, которые будут переиспользоватся в других модулях или это реэкспорт других модулей, которые он в себя импортирует.
Это всё хранится в декораторах, поскольку когда приложение запускается, оно начинает строить дерево зависимостей идя от корневого модуля, он собирает все импорты, провайдеры и контроллеры и строит это дерево.
Первый способ - общие модули
Общие модули можно импортировать в различные модули.
Второй способ - повторный экспорт модулей
Это когда модуль используйщийся промежуточным модулем используется ниже
Третий способ - глобальные модули
Такое может быть необходимо тогда, когда есть модуль, который необходимо использовать во всех модулях приложения. К примеру если используется подключение в базе данных во всех модулях, то можно задать модуль работы базы данных как глобальный и использовать репозиторий базы данных везде без дополнительного импорта модулей. Единственное это то, что глобальный модуль должен быть импортирован в корневой модуль.
Четвертый способ - динамические модули
Часто, особенно при работе с базой данных, необходимо не только получить модуль базы данных, но и передать туда какую-то информацию о подключениях, пользователях и т.п., тогда необходимо использовать динамические модули. Динамические модули - это статический метод модуля, который, в результате, возвращает модуль с вложением провайдера, экспорта и импорта. По умолчанию их необходимо называть forRoot
, если это глобальный или же forFeature
если этот модуль использоватся в определенном скоупе модулей.
Контроллер это входная точка приложения - то место, куда приходят запросы HTTP, MQTT, RabbitMQ, Kаfka и т.д.
Пример контроллера:
Например у нас есть ссылка: http://host.ru/api/product/add/1
.
- Первое что нужно понимать, что на уровне приложения можно задать глобальный префикс апи, с помощью
app.setGlobalPrefix(<api_value>)
. Это очень удобно, поскольку, если у нас на одном домене лежит и фронт и бэк, то фронт будет стучатся на бэк по отдельно выделенному, глобальному, маршруту. - Второе это роутинг, который определяется уже в АПИ. Контроллеры это классы, которые декорированы, декоратором
Controller
. Внутрь этого декоратора мы можем передать путь по которому он будет доступен. - Третье это методы. Они должны быть декорированы соответствующим типом http запроса.
Типы декораторов:
- @Req() - это собственно
request
илиreq
из js. Является объектом запроса. - @Res() - это собственно
response
илиres
из js. Является объектом ответа.
Декораторы, которые можно вытянуть через объект запроса (@Req()):
- @Params(key?: string) - строковый параметр запроса, это может быть например ID.
- @Body(key?: string) - тело запроса.
- @Query(key?: string) - query параметры запроса.
- @Headers(name?: string) - заголовки запроса.
- @Session() - сессия пользователя.
- Реагирования на Wildcard. Это способность помечать места, где можно вставлять любые символы или символы с заданой политикой.
- Свои кастомизированные HTTP коды.
- Свои кастомизированные заголовки.
- Редиректы. В принципе необходимо только в MVC приложениях. В остальных случаях, при перенаправлениях на React или другие фреймворки фронтэнда, то он будет ругатся на CORS.
- Ограничение по поддомену. Если очень большое API, то это можно разграничить с помощью ограничений по поддомену.
- Возврат Promise или Observable. Мы можем вернуть не только строку, но и ещё Promise или Observable, то есть Nest.js сам разрезолвит Promise и вернёт его как ответ.
Помимо того, что мы можем вернуть ответ с помощью return
, мы можем его вернуть с помощью res
. Им можно отправить статус, файл и т.д. Важно понимать, что этот response это не нодовский response. Это response именно express'а.
Провайдеры это клас, который позволяет использовать модель Nest.js по внедрению зависимостей и встраиватся друг в друга, в контроллеры, в сервисы и выполнять те или иные функцию, например чтение из базы данных или запрос к другим сервисам.
Самый простой вид сервиса состоит из класса, в котором есть методы и на который навешан декоратор @Injectable
.
@Injectable
как раз и указывает, что он может быть использован в качестве провайдера и участвовать в дереве зависимостей. Чтобы использовать такой сервис, его достаточно вставить в провайдер модуль providers
и затем в контроллере в конструктор, добавить этот сервис.
использование зависимости
Как только будет создаватся инстанс контроллера, в него будет уже внедрена зависимость от сервиса и его можно будет использовать. Если же необходимо использовать провайдер не в том модуле, в котором он был вставлен, то нужно его экспортить, для того чтобы из этого модуля был доступен этот сервис. Любой модуль который импортирует модуль с экспортируемым сервисом также сможет его использовать.
- Класс - это провайдер используется как класс.
- Фабрика - это проовайдер, который сформирует фабрику, который при необходимости будет формировать нам класс.
- Величина - это конкретное значение, которое можно использовать, например строка подключения.
- Переназывание использующегося провайдера и использовать его с другим псевдонимом.
На самом деле в модуль providers
записывается не только название самого сервиса, но и его тип - useClass
. provide
означает, как будет называтся эта зависимость.
Аналогично с useClass
можно развернуть модуль провайдеров на его название и на его тип. Также необходимо будет провайдить инстанс класса, но его значение можно поменять на величину. Это очень полезно, когда необходимо писать тесты. Например, какой-то сервис должен доставать продукты из базы данных и вместо того, чтобы ходить в реальную базу данных, useValue
позволяет имитировать это хождение в базу данных, замення провайдер на величину.
Также useValue используется в решениях, где DI реализовывается без класса.
В этом случае используется строковое значение, это значение должно быть уникальным иначе это вызовет колизию в приложении. Используется он чуть сложнее, чем класс, поскольку в тех местах, где он будет использоваться необходимо применять декоратор @inject
, и передавать туда строку, которая равна уникального значения и как само значение будет использоватся модуль value
.
Он может использоватся тогда, когда необходимо инициализировать базу данных с некоторыми настройками. Это достаточно частая практика, поскольку внутри самой фабрики необходимо прописать ряд уникальных условий для config, например со строкой подключения. Инными словами useFactory позволяет заинджектить какой-то сервис, потом сделать над ним какую-то логику, после чего вернуть новый инстанс класса с этими параметрами.
На практике практически не используется. Используется когда есть существующий провайдер с именем, которое переименовывается и оно же используется.
Существует три Skope выполнения:
- DEFAUILT
- REQUEST
- TRANSIENT
DEFAUILT - это то, как записывается любой сервис, с пустым @Injectable
с пустым декоратором @Injectable
. Это означает, что на всё приложение будет один инстанс этого сервера. Это классический паттерн Singlton. Это означает, что если какой-то сервис используется в разных местах какой-то другой сервис, то этот сервис будет один и тот же инстанс этого сервиса.
Порой не устраивает, что на каждый запрос будет один инстанс в дереве зависимостей и необходимо использовать на каждый запрос новый инстанс. Например на каждый запрос будет создан отдельный инстанс какого-то сервиса в определённом сервисе. При этом, если какой-то третий сервис зависит от этого, определённого, сервиса, то он получит один и тот же инстанс. Это может быть нужно, если нужен chainable класс. Например когда последовательно вызываются через точку методы, где каждый раз возвращается сам класс, тогда каждый раз нужно иметь отдельный инстанс, чтобы не иметь конфликтов с записью внутренних параметров в этот сервис.
Самый редкий случай, когда в различных сервисах используется один сервис, то в каждом из этих сервисов будет отдельный инстанс этого третъего сервиса.
При отправке каких либо запросов на API с клиента - они могут быть преобразованы через Pipes. В свою же очередь перед тем, как отправить данные на клиент, эти данные могут быть изменены за счёт ExceptionFilter'ов.
Когда происходит ExceptionFilter, то под капотом происходит следующее:
Этот exception преобразовывается Nestjs в JSON. ExceptionFilter позволяют внедрится в процесс обработки между Exception и JSON.
Для того, чтобы сделать свой фильтр нужно создать класс, который будет инплементировать ExceptionFilter и иметь метод сatch
. Также он должен быть навешан декоратором @Catch
, в котором должно быть указан какими exception должны быть обработаны эти фильтры. В рамках метода catch
он будет принимать два значения:
- сам exception.
- host из которого можно получить контекст запроса.
В Nestjs есть уже ряд существующих pipe, которые позволяют преобразовывать существующие данные. Первый это ValidationPipe
, который занимается валидацией. Он может быть внедрён декоратором UsePipes
. Также есть Pipe, которые можно отправить на параметры.
Под капотом ValidationPipe
реализовывает библиотеку validator
и класс трансформер, который позволяет преобразовывать объект в класс, а дальше валидировать свойства этого класса. Реализовывается он очень просто:
При этом Nestjs позволяет валидировать и создавать свой pipe
. Аналогично другим pipe необходимо имплементировать класс PipeTransform
в своём классе и реализовать там метод transorm
, который принимает входящие значения, которые он будет реализовывать и метаданные. Методоанные содержат данные о том, где применяется этот pipe
, применяется он в body, params или где-то ещё, его базовый тип: строка это или что-то другое.
Если необходимо использовать глобальные ExceptionFilter
или Pipe
, то его необходимо подключить к app:
Интерсепторы позволяют перехватывать какой-то запрос и преобразовывать его, для того чтобы в функции можно было сразу преобразовать её во что-то.