
NestJS - это тот фреимворк, созданный для облегчения жизни разработчика, использующий правильные архитектурные подходы и диктующий свои правила.
Поэтому, NestJS- это не только фреимворк для бэкенда, но и возможность войти в мир передовых концепции, например таких как DDD, Event sourcing и микросервисной архитектуре. Все упаковано в простой и легкой форме, так что выбор за вами - решаете ли вы использовать всю платформу или просто использовать ее компоненты.
Для начала расскажу про свой опыт. Долгое время писал на ASP.NET, далее был фронтенд на AngularJS. В октябре 2016 года был переход на Angular и Typescript. И вот оно! Типизация во фронтенде, можно делать сложные вещи достаточно легко! До этого (разработки на nestjs) на node разрабатывал лишь исключительно ради забавы, и как-то даже была попытка внести хорошие практики и typescript в популярный Koa. Но NestJS это все таки немного другое.
NestJS, фреймворк, который полностью написан на TypeScript (он также поддерживает JS, но типы уж очень хороши), он легко тестируется и содержит все необходимое.
Как создать простое приложение на NestJS?
У NestJS под капотом крутится express. Любые расширения для express, легко внедрить в Nest. Но это тут не главное, при большом желании express можно взять и поменять.
Для начала можно скопировать к себе небольшой стартовый набор:
git clone https://github.com/nestjs/typescript-starter.git project
В server.ts включена асинхронная функция, которая отвечает за загрузку нашего приложения:
import { NestFactory } from '@nestjs/core'; import { ApplicationModule } from './modules/app.module'; async function bootstrap() { const app = await NestFactory.create(ApplicationModule); await app.listen(3000); } bootstrap();
Ну и далее запустить npm run start и увидеть на порту 3000 приложение.
Из чего же состоит NestJS?
Автор фреимворка был вдохновлен идеями Angular, и NestJS получился ну очень похожим на Angular, особенно в ранних версиях.
Controllers
Cлой контроллеров отвечает за обработку входящих запросов и возврат ответа клиенту. Простой пример контроллера:
import { Controller, Get } from '@nestjs/common'; @Controller('cats') export class CatsController { @Get() findAll() { return []; } }
Providers
Почти все является Providers — Service, Repository, Factory, Helper и т.д. Они могут быть внедрены в контроллеры и другие провайдеры. Если сказать языком Angular — то это все `@Injectables
Например обычный service:
import { Injectable } from '@nestjs/common'; import { Cat } from './interfaces/cat.interface'; @Injectable() export class CatsService { private readonly cats: Cat[] = []; create(cat: Cat) { this.cats.push(cat); } findAll(): Cat[] { return this.cats; } }
Modules
Модуль - это класс с декоратором Module(). Декоратор Module() предоставляет метаданные, которые Nest использует для организации структуры приложения. Каждое приложение Nest имеет как минимум один модуль, корневой модуль. Корневой модуль - это место, где Nest начинает упорядочивать дерево приложений. Фактически, корневой модуль может быть единственным модулем в вашем приложении, особенно когда приложение маленькое, но это не имеет смысла. В большинстве случаев у вас будет несколько модулей, каждый из которых имеет тесно связанный набор возможностей. В Nest модули по умолчанию являются синглетонами, поэтому вы можете без труда делить один и тот же экземпляр компонента между двумя и более модулями.
import { Module } from '@nestjs/common'; import { CatsController } from './cats.controller'; import { CatsService } from './cats.service'; @Module({ controllers: [CatsController], components: [CatsService], }) export class CatsModule {}
Немного про динамические модули
Модульная система Nest поставляется с функцией динамических модулей. Это позволяет создавать настраиваемые модули без каких-либо усилий. Давайте посмотрим на DatabaseModule:
import { Module, DynamicModule } from '@nestjs/common'; import { createDatabaseProviders } from './database.providers'; import { Connection } from './connection.component'; @Module({ components: [Connection], }) export class DatabaseModule { static forRoot(entities = [], options?): DynamicModule { const providers = createDatabaseProviders(options, entities); return { module: DatabaseModule, components: providers, exports: providers, }; } }
Он определяет компонент Connection по умолчанию, но дополнительно - в зависимости от переданных опций и сущностей - создает коллекцию поставщиков, например, компонентов репозитория. По факту динамический модуль расширяет метаданные модуля. Эта существенная функция полезна, когда вам нужно динамически регистрировать компоненты. Затем вы можете импортировать DatabaseModule следующим образом:
import { Module } from '@nestjs/common'; import { DatabaseModule } from './database/database.module'; import { User } from './users/entities/user.entity'; @Module({ imports: [ DatabaseModule.forRoot([User]), ], }) export class ApplicationModule {}
Кстати, для работы с базой есть крутой TypeORM, умеющий работать с большинством баз данных.
Middlewares
Middlewares - это функция, которая вызывается перед обработчиком роута. Они имеют доступ к request и response. По сути они являются такими же как и в express.
import { Injectable, NestMiddleware, MiddlewareFunction } from '@nestjs/common'; @Injectable() export class LoggerMiddleware implements NestMiddleware { resolve(...args: any[]): MiddlewareFunction { return (req, res, next) => { console.log('Request...'); next(); }; } }
Exception Filters
В Nest есть слой исключений, в обязанности которого входит перехват необработанных исключений и возврат соответствующего ответа конечному пользователю.
Каждое исключение обрабатывается глобальным фильтром исключений, и когда оно не распознается (не HttpException или класс, который наследует HttpException), пользователь получает следующий ответ JSON:
{ "statusCode": 500, "message": "Internal server error" }
Pipes
Pipe должен реализовывать интерфейс PipeTransform.
import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common'; @Injectable() export class ValidationPipe implements PipeTransform { transform(value: any, metadata: ArgumentMetadata) { return value; } }
Pipe преобразует входные данные в желаемый результат.
Кроме того, это может сойти за валидацию, так как им же возможно генерировать исключение, если данные неверны. Например:
@Post() @UsePipes(new ValidationPipe(createCatSchema)) async create(@Body() createCatDto: CreateCatDto) { this.catsService.create(createCatDto); }
Или же можно обьявить глобальный pipe:
async function bootstrap() { const app = await NestFactory.create(ApplicationModule); app.useGlobalPipes(new ValidationPipe()); await app.listen(3000); } bootstrap();
Guards
Guards должны реализовывать интерфейс CanActivate. Guards имеют единственную ответсвенность. Они определяют, должен ли запрос обрабатываться обработчиком маршрута или нет.
@Injectable() export class RolesGuard implements CanActivate { canActivate( context: ExecutionContext, ): boolean | Promise<boolean> | Observable<boolean> { // const request = context.switchToHttp().getRequest(); // const data = context.switchToWs().getData(); return true; } } Использование: <source lang="javascript"> @Controller('cats') @UseGuards(RolesGuard) export class CatsController {}
Interceptors
Перехватчики имеют ряд полезных возможностей, которые вдохновлены техникой Aspect-Oriented Programming (AOP). Они позволяют:
- привязать дополнительную логику до / после выполнения метода;
- преобразовать результат, возвращаемый функцией;
- преобразовать исключение, выброшенное из функции;
- полностью переопределить функцию в зависимости от выбранных условий (например, для кэширования).
Микросервисы
Микросервис Nest - это просто приложение, которое использует другой транспортный уровень (не HTTP).
Nest поддерживает два типа связи - TCP и Redis pub/sub, но новую транспортную стратегию легко внедрить, реализовав интерфейс CustomTransportStrategy.
Вы легко можете создать микросервис из своего приложения:
import { NestFactory } from '@nestjs/core'; import { ApplicationModule } from './modules/app.module'; import { Transport } from '@nestjs/microservices'; async function bootstrap() { const app = await NestFactory.createMicroservice(ApplicationModule, { transport: Transport.TCP, }); app.listen(() => console.log('Microservice is listening')); } bootstrap();
Микросервис Nest распознает сообщения по шаблонам. Шаблон представляет собой простое значение, объект, строку или даже число.
import { Controller } from '@nestjs/common'; import { MessagePattern } from '@nestjs/microservices'; @Controller() export class MathController { @MessagePattern({ cmd: 'sum' }) sum(data: number[]): number { return (data || []).reduce((a, b) => a + b); } }
А для общения между микросервисами необходимо использовать клиент:
@Client({ transport: Transport.TCP, port: 5667 }) client: ClientProxy;
А вот так будет выглядеть отправка сообщения:
@Get() call(): Observable<number> { const pattern = { cmd: 'sum' }; const data = [1, 2, 3, 4, 5]; return this.client.send<number>(pattern, data); }
NestJS и Angular так сильно вместе связаны, что автора фреймворка можно легко встретить на ng конференциях и митапах. Например, недавно команда nrwl включила в свой nx шаблон nestjs.
ng g node-app nestjs-app -framework nestjs
NestJS уже достаточно зрел, и многие компании уже используют его.
Кто сейчас использует NestJS в продакшне?
Сам фреймворк: https://github.com/nestjs/nest
Много крутых ссылок по теме здесь:Awesome-nestjs
Русскоязычное сообщество NestJS в телеграмме https://t.me/nest_ru
Русскоязычный доклад про NestJS.
Ну и конечно подписывайтесь на канал в телеграмме @ngFanatic где есть новости про NestJS и Angular.
P.S.: Это только часть возможностей NestJS, про личный опыт, длиною в год, будет отдельная статья.
