NestJS - тот самый, настоящий бэкенд на nodejs
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 [];
}
}
Components
Почти все является компонентом — Service, Repository, Factory, Helper и т.д. Они могут быть внедрены в контроллеры и другие компоненты. Если сказать языком Angular — то это все @Injectables
import { Component } from '@nestjs/common';
import { Cat } from './interfaces/cat.interface';
@Component()
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 { Middleware, NestMiddleware, ExpressMiddleware } from '@nestjs/common';
@Middleware()
export class LoggerMiddleware implements NestMiddleware {
resolve(...args: any[]): ExpressMiddleware {
return (req, res, next) => {
console.log('Request...');
next();
};
}
}
Exception Filters
В Nest есть слой исключений, в обязанности которого входит перехват необработанных исключений и возврат соответствующего ответа конечному пользователю.
Каждое исключение обрабатывается глобальным фильтром исключений, и когда оно не распознается (не HttpException или класс, который наследует HttpException), пользователь получает следующий ответ JSON:
{
"statusCode": 500,
"message": "Internal server error"
}
Pipes
Pipe — это класс с декоратором Pipe (). Pipe должен реализовывать интерфейс PipeTransform.
Pipe преобразует входные данные в желаемый результат.
Кроме того, это может сойти за валидацию, так как им же возможно генерировать исключение, если данные неверны. Например:
@Post()
async create(@Body(new ValidationPipe()) 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 имеют единственную ответсвенность. Они определяют, должен ли запрос обрабатываться обработчиком маршрута или нет.
import { Guard, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs/Observable';
@Guard()
export class RolesGuard implements CanActivate {
canActivate(dataOrRequest, context: ExecutionContext): boolean | Promise | Observable {
return true;
}
}
Использование:
@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 {
const pattern = { cmd: 'sum' };
const data = [1, 2, 3, 4, 5];
return this.client.send(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.
Ну и конечно подписывайтесь на канал https://t.me/ngFanatic где есть новости про NestJS и Angular.
P.S.: Это только часть возможностей NestJS, про личный опыт, длиною в год, будет отдельная статья.