Добрый день. В нашей компании мы очень трепетно относимся к архитектуре наших решений и удобству ее использования. Мы стараемся убирать узкие места для получения лучшей производительности, а также для сокращения времени разработки новых фич. Одним из таких способов мы решили поделиться с вами.

Сегодня поговорим о фронт-оптимизации, об express, nest.js и о том, как этим правильно пользоваться. Думаю, все уже обратили внимание на тенденцию, которой следуют фронтовые разработчики: делать бек на node.js. Это значит, что для фронта настало золотое время – давайте разберёмся почему. Сразу отмечу, что я нарочно буду избегать разбора механизмов работы node.js, а также всех его плюсов и минусов, так как сегодня речь пойдет не об этом.

Зачем писать бек на node.js?

Исходя из своего опыта, могу сказать, что эта программная платформа применяется в случаях, когда требуется вынести часть бизнес-логики в прослойку между историческим беком и свежим фронтом. Я встречал ситуации, где node.js служил инструментом для перехода от одного типа архитектуры (монолит) к другому (микросервисной). В качестве промежуточного вывода можем заключить: node.js позволяет нам создавать унифицированное API, если по какой либо причине бек не может выполнить свою работу.

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

Express

Большинство приложений написано на простом и минималистичном фреймворке express.js. Он прост в освоении, гибок и даже вроде как позволяет всё сделать легко. Однако у него есть недостатки, о которых расскажу далее.

Первый и самый главный — гибкость. Это означает, что при отсутствии навыков его использования вы или совершите ошибки, или случайно откроете временной портал.

Второй недостаток — импорт. Данный функционал реализован не слишком нативно, поэтому если вы просчитались с архитектурой, то мешанины вам не избежать.

Третий недостаток состоит в том, что большинство проверок на тип и пустоту требуется осуществлять вручную, так как в express.js применяется ES, а не TS. Также к минусам отнесу пляски с выставлением кукис и прочим серверным взаимодействием со стороны UI.

Все вышеперечисленное свидетельствует о том, что в express.js используется устаревший паттерн callback-функций, что в свою очередь зачастую приводит к печальным последствиям. Ещё раз напомню, что использование express.js в чистом виде практически невозможно, за исключением каких-то простых приложений. Поэтому я предлагаю взамен ему использовать nest.js.

Nest.js

Nest.js — фреймворк для создания серверных приложений на node.js, расскажу о его особенностях, плюсах и минусах.

Nest.js написан на TypeScript и полностью поддерживает его (даже современные версии). Что это нам дает?

  • Проверку на тип данных: как простых, так и сложных. Enum и прочие опции сильно выручают.

  • Декораторы – функционал декларативного программирования, позволяющий расширить любые методы, какие только пожелаем.

  • Классические импорты TypeScript (до боли знакомые разработчикам Angular). Побочным эффектом послужит ускорение разработки, а также возможность писать код большой командой, не мешая друг другу.

  • Четкое разделение функциональной нагрузки по элементам:

    • Interceptor — отвечает за дополнение, перехват запросов.

    • Guards — отвечает за проверки доступности по каким-либо критериям.

    • Pipes — выполняет две роли. Первая — это преобразование одного типа в другой. Например, когда у вас ID ожидается строкой, а пришло число, и чтобы не страдать преобразованием типов на фронте, pipe реализует это на беке. Вторая функция — это проверка соответствия типов.

    • Custom route decorators — если вы считаете себя гениями, которым море по колено, то можете создать что-то жизненно необходимое.

    • Exception filters — это все исключения, которые могут быть (согласно сетевому взаимодействию). В тех случаях, когда мы забыли сделать кастомную обработку ошибок, мы все равно получим данные по ним.

    • Middleware — предобработка запросов с доступами ко всей информации запроса.

    • Modules — используется для организации кода и архитектуры приложения.

    • Controllers — отвечает за обработку входящих запросов и возврат ответов клиенту. А также за роутинг, парсинг параметров, в общем за всё, что касается принятия и отдачи по запросу.

    • Nest.js поддерживает любые протоколы в добавок к HTTP, например, на основе RabbitMQ, Nats, Kafka или даже просто TCP-протокола.

      Все это доступно просто из коробки, без плясок с бубном, до установки модулей, плагинов, расширений и прочего. А еще присутствует CLI — это значит, что с помощью командной строки код у нас будет генерироваться, как нужно, и прописываться везде, где нужно, что, в свою очередь, значительно ускорит работу. Не правда ли, звучит знакомо? Особенно для тех, кто хоть когда-то писал на Angular.

      Теперь о плюсах и минусах.

      Плюсы:

    • Легко использовать, учиться и осваивать.

    • Мощный интерфейс командной строки для повышения производительности и упрощения разработки.

    • Подробная и отлаженная документация с примерами (можно прочесть на https://nestjs.com/, а еще у них есть платные курсы, первый раз такое вижу).

    • Открытый исходный код.

    • Простые приложения для модульного тестирования.

    • Создан для монолитов и микросервисов.

      Минусы:

    • Требуется выучить большой объём информации, чтобы правильно все использовать.

    • Придётся много писать DTO, для того чтобы данные корректно обрабатывались в запросе.

    • Придётся иногда делать множественные проверки по типам, например, Enum и прочие.

      Резюмируя: Nest.js — прекрасный инструмент, который позволяет делать много, быстро, а главное, качественно, в обмен на хорошее описание DTO и прочих объектов. По мне, это небольшая плата за то, что снимает с меня львиную долю головной боли по проверкам, ответам, дополнению запросов и прочей рутины. И вот вам еще хинт, как из минусов выжать максимальное количество плюсов ;)

Как правильно пользоваться Nest.js

  • Axios — потребуется для перехвата и подсовывания в него мок (для тестов самое то).

  • @apidevtools/swagger-parser — позволяет автогенерировать код со swagger бека для упрощения написания интерфейсов, что значительно ускоряет написание кода.

  • Swagger-ui-express — позволяет создать свой swagger и тестировать свою документацию (можно отдельно отдавать на тестирование).

  • Swagger-typescript-api — позволяет спарсенный код swagger преобразовать в TypeScript и использовать для нужд проекта.

А еще пригодится https://editor.swagger.io/, в него можно вставить то, что сгенерировал наш swagger. Если кликнуть на Generate Client, а затем на typescript-angular, мы получим архив с автосгенерированными сервисами и интерфейсами для нашего приложения. Этот же архив можно использовать для react на тайп-скрипте. Если вы его внимательно изучите, то обнаружите, что он много для чего может подойти, чем облегчит вам жизнь и ускорит разработку.

P.S. Таков мой опыт использования фреймворков node.js, надеюсь, он окажется для вас полезным. С радостью отвечу на все ваши вопросы.

Автор: Дмитрий Ивко, ведущий разработчик Центра продуктов Dozor компании "РТК-Солар"

Комментарии (25)


  1. ReadOnlySadUser
    17.05.2022 11:47
    +4

    Я может сейчас чего-то не понял, но где хоть одно слово про архитектуру фронта в этой статье?


    1. Hrodvitnir
      17.05.2022 12:35
      +1

      Ну да

      Не смотря на полезность статьи – название абсолютно не отображает ее сути:)


      1. SolarSecurity
        17.05.2022 12:56

        Поменяли, приносим извинения :)


    1. SolarSecurity
      17.05.2022 12:47

      Добрый день. Смысл статьи в том, что необходимо разделять бизнес логику и  ui логику.  В целом, это первая статья из цикла статей по оптимизации и подходам на фронте. Для начала мы рассказали, как сделать жизнь легче путем автоматизации  некоторых процессов (автогенерацию). В планах - рассказ, как сделать быстрый ui, затем, как все соединить в единое целое. Для ясности постараемся четче подбирать названия для статей.


      1. ReadOnlySadUser
        17.05.2022 14:21
        +1

        Да я вообще не сварщик, я на С++ пишу :) Просто зашёл почитать про архитектуру, мало ли какой-нибудь прикольный архитектурный принцип завезли, можно будет подумать над тем, имеет ли предлагаемая архитектура право на жизнь в других языках, а тут просто обзор двух фреймворков. Сейчас название получше)


  1. Pab10
    17.05.2022 12:26

    Декораторы – функционал декларативного программирования, позволяющий расширить любые методы, какие только пожелаем.

    Это не функционал, а синтаксический сахар. В жс функции высшего порядка, так что декораторы встроены изначально.


  1. jonezq
    17.05.2022 14:02

    Третий недостаток состоит в том, что большинство проверок на тип и пустоту требуется осуществлять вручную, так как в express.js применяется ES, а не TS. Также к минусам отнесу пляски с выставлением кукис и прочим серверным взаимодействием со стороны UI.

    ES? Что мешает использовать express с TS? какие проблемы у express с cookies? И как это решает nest?

    Прочим серверным взаимодействием - что конкретно не так, и как это решает nest?

    Почему axios, rest, swagger, angular

    Тупо кликбейт


    1. SolarSecurity
      17.05.2022 14:11
      +1

      Во-первых, из коробки express не поддерживает TS, нужны костыли и прочее. Сама структура приложений на экспрессе не предусматривает  использования всех фишек TS, что ведет к снижению его эффективности.

      С кукис проблема следующего характера: их на экспрессе надо постоянно парсить при каждом запросе, затем, если надо, выставлять и совершать прочие манипуляции.  Упрощение работы с кукис происходит только тогда, когда мы прикручиваем сторонние плагены, но опять же это сторонние зависимости.

       У nest есть базовые компоненты для работы с кукис, например, интерсептор помогает подсовывать их в автоматическом режиме при запросе к беку,  или в контролерах  использовать через контекст при ответах на ui. 

      Почему axios, rest, swagger, angular. Потому что axios позволяет нам перехватывать запросы и подсовывать мок, что упростит нам тестирование в автоматическом режиме. Rest - самый используемый паттерн  взаимодействия.  А это значит, что он будет большинству полезен. Но nest умеет работать почти со всем.

      Swagger - самый удобный инструмент для самодокументированного кода, что позволяет протестить бек, прослойку между беком и ui , спокойно, без лишних плясок. 

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


      1. jonezq
        17.05.2022 16:01
        +1

        С каких пор добавление поддержки TS стало костылем? У express есть типы на TypeScript, которые зачастую используются в nestjs, nestjs все лишь набор оберток вокгур популярных решений(в том числе и express)

        interceptor - это middleware которое есть в express, в express обычно так же мутируют реквест(точнее nestjs делает это так же как в express, fastify использует немного другую технику, но тут о нем речи нет)

        да есть куча тулов и дока по несту имеет хорошее описание, так почему именно эти - в чем их эффективность?

        Первый и самый главный — гибкость. Это означает, что при отсутствии навыков его использования вы или совершите ошибки, или случайно откроете временной портал.

        дереватив гибче родителя? - ну ок

        Второй недостаток — импорт. Данный функционал реализован не слишком нативно, поэтому если вы просчитались с архитектурой, то мешанины вам не избежать.

        чего?

        серьезно, я сам использую nest, но титул - как эффективно использовать nest.js, сводится к полуярным либам, и к сложности добавления tsconfig к экспрессу.

        если использвать TS c express количество dto станет меньше?


        1. Evil_Evis
          17.05.2022 20:28

          С каких пор добавление поддержки TS стало костылем? У express есть типы на TypeScript, которые зачастую используются в nestjs, nestjs все лишь набор оберток вокгур популярных решений(в том числе и express)

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

          interceptor - это middleware которое есть в express, в express обычно так же мутируют реквест(точнее nestjs делает это так же как в express, fastify использует немного другую технику, но тут о нем речи нет)

          Его можно сгенерировать автоматически? он удобнее? Где его проще написать? Вопрос не в том что где есть, а где удобнее. Подход почти везде одинаков, исполнение везде разное. Как говориться дьявол, кроиться в деталях.

          да есть куча тулов и дока по несту имеет хорошее описание, так почему именно эти - в чем их эффективность?

          в статье вроде ни где не указано что они единственные и неповторимые, просто автор привел то с чем работает. Есть другие на примете, давайте делиться. С радостью прочитаю обзор и сравнение на них.

          Первый и самый главный — гибкость. Это означает, что при отсутствии навыков его использования вы или совершите ошибки, или случайно откроете временной портал.

          Чем гибче инструмент тем больше ответственность за архитектуру, можно натворить циклических зависимостей и прочего ненужного. Фреймворк - забирает гибкость но в замен дает стандарт использования. Так как фреймворк имеет четко очерченную зону ответственности у каждой его части.

          Второй недостаток — импорт. Данный функционал реализован не слишком нативно, поэтому если вы просчитались с архитектурой, то мешанины вам не избежать

          автор имел ввиду require.

          серьезно, я сам использую nest, но титул - как эффективно использовать nest.js, сводится к полуярным либам, и к сложности добавления tsconfig к экспрессу.

          если использвать TS c express количество dto станет меньше?

          Что-то вас не понял, причем количество DTO, вопрос в том что можно автоматизировать данный процесс. С радостью выслушаем ваш опыт использования и советы по использованию.


          1. jonezq
            18.05.2022 15:55

             С радостью выслушаем ваш опыт использования и советы по использованию.

            куда мне-то давать советы, я умею только в доку, зашел почитать как использовать оптимальнее


  1. webdevium
    17.05.2022 14:42
    +1

    Мне кажется, что сравнивать micro фреймворк и full-stack фреймворк — кощунство.

    Это как сравнивать sinatra.rb и Ruby on Rails.


    1. Evil_Evis
      17.05.2022 20:06

      ну как-то сравнивают же реакт и ангуляр? По сути тоже самое. Они оба выполняют одну и туже функцию. Вопрос удобства и порога вхождения


  1. return
    17.05.2022 14:53

    Как эффективно использовать-то? )


    1. Evil_Evis
      17.05.2022 20:04

      тут вроде рассказывается что и как. Приведенные пакеты помогут автоматизировать, алгоритм прост.

      Перечень действий:

      1. устанавливаем пакеты

      2. с помощью @apidevtools/swagger-parser мы парсим свагер бека для того что получит JSON для обработки

      3. потом его переводим в удобно используемый код с помощью Swagger-typescript-api

      4. далее при помощи Swagger-ui-express мы сможем иметь свагер нашего BFF

      5. А для автоматизации тестирования что бы подставлять моки (перехват запросов) используем Axios

      выше изложенные действия экономят нам кучу времени на описания интерфейсов и прочего


      1. return
        17.05.2022 20:06

        в чем тут эффективность и относительно чего?


        1. Evil_Evis
          18.05.2022 16:24
          -1

          как минимум скорости написания кода, разве нет? Качество кода...


  1. Suvitruf
    17.05.2022 19:31

    Сравнивать Express, который, по сути, просто библиотека, с полноценным фреймворком в виде Nest.js, это какое-то издевательство над первым ????


    1. Evil_Evis
      17.05.2022 19:38

      Ну тут вопрос реторический как реакт и ангуляр. Но реакт разработчики говорят что ангуляр не ровня.


    1. martovsky
      18.05.2022 10:41
      +1

      Особенно смешно читать по причине того, что nest использует как раз express.


  1. Suvitruf
    17.05.2022 19:34

    Требуется выучить большой объём информации, чтобы правильно все использовать.
    Да нет. Его прелесть как раз в том, что стартовый проект легко заводится. Нет необходимости досконально знать каждый аспект/компонент, а можно изучать по мере необходимости.

    Придётся много писать DTO, для того чтобы данные корректно обрабатывались в запросе.
    Странно жаловаться на это. Это как жаловаться на возможность/необходимость использовать типы в ts.

    Придётся иногда делать множественные проверки по типам, например, Enum и прочие.
    Можно подробнее о каких проверках речь?


    1. Evil_Evis
      17.05.2022 19:56

      Да нет. Его прелесть как раз в том, что стартовый проект легко заводится. Нет необходимости досконально знать каждый аспект/компонент, а можно изучать по мере необходимости.

      Большой объём инфы, по сравнению с express. А так же не надо забывать что плоха можно написать везде, а вот правильно не всегда просто. Как пример Interceptor это же по сути сервис, и его функциональность можно руками сунуть во внутрь

      Странно жаловаться на это. Это как жаловаться на возможность/необходимость использовать типы в ts.

      Это не жалоба, это просто примечание того что boilerplate много писать придаться в части DTO. Как пример доскональное описание полей а так же их проверок.

      Можно подробнее о каких проверках речь

      пример описания пары полей ниже, просто представите то у вас порядка 50 полей и на каждое надо описание как ниже

      // Пример описания ответа
      @ApiProperty({
          description: 'Код сообщения',
          type: String,
          example: 'USERS_001',
      })
      code?: string;
      
      @ApiProperty({
          description: 'Уровень сообщения',
          enum: getEnumValues(SelectedLevelTypes), /сравнение с Enum
          required: false,
          example: SelectedLevelTypes.INFO,
      })
      @IsEnum(SelectedLevelTypes)
      level?: SelectedLevelTypes;
      
      @ApiProperty({
          description: 'Текст сообщения',
          type: String,
          example: 'Catalog service does not reply',
      })
      text?: string;
      
      //  описание одного поля для входного параметра 
      @ApiProperty({
          description: 'Координаты',
          type: GeolocationAddresses,
          required: false,
      })
      @IsOptional()  / параметр не обязателен в  запросе
      @Type(() => GeolocationAddresses) // что данное поле имеет сложное тип (тоесть
      вложенность)
      @ValidateNested({ each: true }) // включение проверки вложенных типов
      geolocation?: GeolocationAddresses;
      


      1. Suvitruf
        17.05.2022 21:01

        пример описания пары полей ниже, просто представите то у вас порядка 50 полей и на каждое надо описание как ниже
        Так это же для свагера. В express'е не меньше бы писать пришлось.


        1. Evil_Evis
          18.05.2022 09:50

          не совсем оно так, к свагеру относиться только код нижу

          @ApiProperty({
              description: 'Координаты',
              type: GeolocationAddresses,
              required: false,
          })

          А все остальное это пайпы самого nestjs. О которых вы можете прочитать здесь. Так же пайпов может быть довольно-таки много если сложные проверки например, когда одно поле зависит от другого.


          1. jonezq
            19.05.2022 16:40

            причем здесь пайпы - это обычные dto объекты которые будут в любом статически типизированном бэкенде. То что вам надо десерилизовать данные из запроса - в каком-нибдуь dotnetcore этого делать не надо? да надо

            большие проекты на express и nestjs по структуре сильно похожи, теже +/-тулы, по сути команда nestjs обобщила хорошие практики из бэкендов на ноде и сделала удобный враппер, как next вокруг react