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, про личный опыт, длиною в год, будет отдельная статья.