Pull to refresh

NestJS - тот самый, настоящий бэкенд на nodejs

Reading time6 min
Views165K
image

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, про личный опыт, длиною в год, будет отдельная статья.
Tags:
Hubs:
Total votes 23: ↑22 and ↓1+21
Comments25

Articles