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

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

    Комментарии 24

    • НЛО прилетело и опубликовало эту надпись здесь
        0

        Да, первый нормальный фреймворк для Ноды. Я бы еще сразу упомянул @nestjs/websockets — с вебсокетами там очень удобно сделано.


        А вот насчет крутости TypeORM я позволю себе усомниться, там куча совершенно детских проблем. Для всего, конечно, есть workaround-ы, но как я матерился в первый раз, как я матерился, особенно обнаружив, что при гидрации вызывается конструктор сущности :-) Но альтернатив пока нет все равно.

          0
          как это нет? есть sequelize, который легко подменяет TypeOrm в несте
            0

            Sequelize — это штука для persistence models. Мне бы для entities, которые толстые и с бизнес-логикой.

            0
            а вот я бы не позволял себе сомневаться ;)
            0
            сначала очень напоминает Spring MVC/Spring Boot
              0
              Самый лучший фреймвор для ноды. К сожалению, не очень популярный, имеет все шансы не завоевать популярность. Скрестим пальчики и помолимся за этот чудесный фреймворк.
                0

                Считаю, что у него, напротив, все шансы завоевать популярность. Его, конечно, никто не пиарит из каждого утюга (как было с Laravel в свое время), но он занимает пустующую нишу полноценного, ориентированного на классический ООП-подход фреймворка для Ноды. Наверняка почти каждый, кто разрабатывал на Node.JS и Typescript достаточно сложное приложение, приходил к написанию мини-фреймворка вокруг Express, так или иначе напоминающего привычные по другим языкам MVC-фреймворки. А тут все из коробки, и сделано очень хорошо.

                  +1
                  По моим ощущениям, просто в сообществе JS не очень принято ООП, да пишут классы, но это какое-то странное ООП, не такое которое я видел на бэкенде.

                  Аналогичная ситуация с Дартом кстати, бывшие JS разработчики, которых заставляют переходить на него, дико хейтят этот ЯП.
                    +2

                    JS-сообщество сегодня и завтра — это не JS-сообщество вчера. Те, кто вчера писал на PHP+Symfony или Java+Spring, сегодня-завтра уже пишут на Typescript (это по личным наблюдениям).

                      0
                      Еще те кто писал на Python/Ruby/C++ и вообще пришел в программирование извне
                0
                Фреймворк очень крутой, однако… Толи из-за того, что он ещё не очень популярный и мало контрибьюторов, толи из-за чего-то другого в нем обнаружилась тонна проблем в банальных местах. Я так и не смог победить всё, когда пытался Nest завести с Angular Universal и докинуть туда небольшое API. Модуль swagger и nguniversal по какой-то причине конфликтуют и сваггер просто не собирается, типы под passport были неправильные из коробки и соответственно ничего не работало, перечисление руками каждой энтити TypeORM прямо в модуль дико бесило.

                Я надеюсь этот фреймворк выстрелит, найдет крутое коммьюнити, будет развиваться и избавится от всех косяков.
                  +1
                  Год назад я немного игрался с NestJS, тогда он мне показался немного не полным.
                  Сейчас использую Ts.ED tsed.io
                  • НЛО прилетело и опубликовало эту надпись здесь
                      +1

                      Подозреваю, что вы просто не до конца разобрались. Да, документация не столь полна как хотелось бы, и временами надо читать исходники Неста, но нет никаких проблем в том, чтобы вашу реализацию, какой бы там она ни была, оформить в виде NestJS-модуля.


                      Если хотите, можете выложить вашу реализацию на тот же Gist, а я покажу, как ее можно интегрировать с NestJS.

                      • НЛО прилетело и опубликовало эту надпись здесь
                    0

                    В чем отличие по сравнению с Next.js?

                      +1

                      Это будет сравнение теплого с мягким.
                      Next.js предназначен, прежде всего, для серверного рендеринга, и привязан к React. NestJS — для разработки бэкенда, с которым фронтенд общается через GraphQL или REST, и ему все равно, как реализован фронтенд.

                      0
                      Использую NestJS уже на втором проекте и пока ощущения только положительные. Особенно если вы знакомы с Angular 2+, то это прям вообще песня, т.к. некоторые идеи взяты именно оттуда. Единственное, над чем пришлось потрудиться, это над сторонней валидацией, она на мой взгляд недостаточно гибкая
                        0
                        Первый энтерпрайзный бекенд-фреймворк на JS. Просто огонь!
                          0
                          До этого много лет на чистой ноде писал. На текущем месте работы начал на NestJS и TypeScript пилить новые сервисы. Есть небольшие нюансы, недостаток документации и отсутствие примеров, но в целом впечатления положительные.
                            0
                            статика такая статика )
                            настоящий нод это js а ваш тайпскрипт с «правильными» — легаси, вид сбоку.
                            но проще верблюду пролезть в утиную типизацию чеи старому джависту доказать ненужность ООП и статики

                            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                            Самое читаемое