PurpleSchool — курсы программирования онлайн
  • Бесплатно
    • Курсы
    • JavaScript Основы разработкиPython Основы PythonCSS CSS FlexboxКарта развития
    • База знанийИконка стрелки
    • Новостные рассылкиИконка стрелки
  • Карьерные пути
    • Frontend React разработчик
    • Frontend Vue разработчик
    • Backend разработчик Node.js
    • Fullstack разработчик React / Node.js
    • Mobile разработчик React Native
    • Backend разработчик Golang
    • Devops инженер
    • Backend разработчик Python
  • О нас
    • Отзывы
    • Реферальная программа
    • О компании
    • Контакты
  • Иконка открытия меню
    • Сообщество
    • PurpleПлюс
    • AI тренажёр
    • Проекты
PurpleSchool — платформа бесплатных roadmap и курсов для разработчиков
ютуб иконка
Telegram иконка
VK иконка
VK иконка
Курсы
ГлавнаяКаталог курсовFrontendBackendFullstack
Практика
КарьераПроектыPurpleПлюс
Материалы
БлогБаза знаний
Документы
Договор офертаПолитика конфиденциальностиПроверка сертификатаМиграция курсовРеферальная программа
Реквизиты
ИП Ларичев Антон АндреевичИНН 773373765379contact@purpleschool.ru

PurpleSchool © 2020 -2026 Все права защищены

  • Курсы
    • FrontendИконка стрелки
    • AI разработкаИконка стрелки
    • BackendИконка стрелки
    • DevOpsИконка стрелки
    • MobileИконка стрелки
    • ТестированиеИконка стрелки
    • Soft-skillsИконка стрелки
    • ДизайнИконка стрелки
    Иконка слояПерейти в каталог курсов
  • PurpleSchool — курсы программирования онлайн
    • Сообщество
    • PurpleПлюс
    • AI тренажёр
    • Проекты
    Главная
    Сообщество
    NestJS: как организовать модули, чтобы проект не стал монолитом

    NestJS: как организовать модули, чтобы проект не стал монолитом

    Аватар автора NestJS: как организовать модули, чтобы проект не стал монолитом

    Антон Ларичев

    Иконка календаря28 марта 2026
    nestjstypescriptarchitecturemiddleИконка уровня middle
    Картинка поста NestJS: как организовать модули, чтобы проект не стал монолитом

    Введение

    Организация модулей в NestJS — это фундамент, на котором строится масштабируемость всего проекта. Когда модульная архитектура NestJS выстроена правильно, добавление новых функций не превращается в болезненную переработку половины кодовой базы. Но если структуру проекта NestJS запустить с самого начала, через полгода вы получите монолит, где все зависит от всего.

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

    Почему модульная архитектура NestJS так важна

    NestJS изначально спроектирован вокруг концепции модулей. Декоратор @Module() — это основной строительный блок приложения. Он группирует контроллеры, сервисы и провайдеры в логически связанные единицы.

    Проблема в том, что многие разработчики создают модули формально: один модуль на одну сущность базы данных. В итоге UserModule импортирует OrderModule, который импортирует PaymentModule, а тот снова зависит от UserModule. Появляются циклические зависимости, и проект теряет модульность.

    // Плохо: модуль привязан к сущности, а не к домену
    @Module({
      imports: [TypeOrmModule.forFeature([User])],
      controllers: [UserController],
      providers: [UserService],
      exports: [UserService], // экспортируем всё подряд
    })
    export class UserModule {}
    

    Разделение модулей NestJS по доменным областям

    Вместо создания модуля на каждую сущность, группируйте код по бизнес-доменам. Домен — это область бизнес-логики, которая может существовать относительно независимо.

    Например, вместо отдельных UserModule, ProfileModule и AvatarModule создайте один AccountModule, объединяющий всё, что связано с учетной записью пользователя:

    // Хорошо: модуль организован по домену
    @Module({
      imports: [TypeOrmModule.forFeature([User, Profile, Avatar])],
      controllers: [AccountController, ProfileController],
      providers: [
        AccountService,
        ProfileService,
        AvatarService,
      ],
      exports: [AccountService], // экспортируем только публичный API
    })
    export class AccountModule {}
    

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

    Как правильно использовать exports и imports в модулях

    Главное правило: экспортируйте из модуля минимум. Каждый экспортированный провайдер — это публичный контракт, который потом сложно изменить.

    @Module({
      imports: [AccountModule, ProductModule],
      controllers: [OrderController],
      providers: [
        OrderService,
        OrderRepository,
        PriceCalculator, // внутренний провайдер, не экспортируем
      ],
      exports: [OrderService], // только фасад для других модулей
    })
    export class OrderModule {}
    

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

    Shared-модули для общей функциональности

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

    @Module({
      providers: [LoggerService, CacheService, ConfigService],
      exports: [LoggerService, CacheService, ConfigService],
    })
    export class SharedModule {}
    

    Если модуль используется почти везде, можно сделать его глобальным через декоратор @Global():

    @Global()
    @Module({
      providers: [ConfigService],
      exports: [ConfigService],
    })
    export class ConfigModule {}
    

    Но не злоупотребляйте @Global(). Глобальные модули нарушают явность зависимостей — непонятно, откуда берется провайдер. Используйте глобальные модули только для инфраструктурных вещей.

    Динамические модули для гибкой конфигурации

    Динамические модули позволяют настраивать поведение при импорте. Это удобно для библиотечных модулей, которые должны работать с разными параметрами:

    @Module({})
    export class DatabaseModule {
      static forRoot(options: DatabaseOptions): DynamicModule {
        return {
          module: DatabaseModule,
          providers: [
            {
              provide: 'DATABASE_OPTIONS',
              useValue: options,
            },
            DatabaseService,
          ],
          exports: [DatabaseService],
          global: true,
        };
      }
    
      static forFeature(entities: Type[]): DynamicModule {
        return {
          module: DatabaseModule,
          providers: entities.map((entity) => ({
            provide: getRepositoryToken(entity),
            useFactory: (db: DatabaseService) => db.getRepository(entity),
            inject: [DatabaseService],
          })),
          exports: entities.map((entity) => getRepositoryToken(entity)),
        };
      }
    }
    

    Паттерн forRoot / forFeature позволяет разделить инициализацию (один раз в AppModule) и использование (в каждом фича-модуле).

    Структура проекта NestJS для масштабируемости

    Рекомендуемая структура директорий для среднего и крупного проекта:

    src/
      modules/
        account/
          account.module.ts
          account.controller.ts
          account.service.ts
          dto/
          entities/
        order/
          order.module.ts
          order.controller.ts
          order.service.ts
          dto/
          entities/
        payment/
          payment.module.ts
          payment.controller.ts
          payment.service.ts
      shared/
        shared.module.ts
        logger/
        cache/
      config/
        config.module.ts
      app.module.ts
      main.ts
    

    Каждый доменный модуль содержит всё необходимое: контроллеры, сервисы, DTO и сущности. Это позволяет при необходимости вынести модуль целиком в отдельный микросервис.

    Частые ошибки при организации модулей

    Модуль на каждую сущность. Создание UserModule, UserProfileModule, UserSettingsModule по отдельности приводит к десяткам мелких модулей с перекрестными зависимостями. Объединяйте связанные сущности в один доменный модуль.

    Циклические зависимости. Если ModuleA импортирует ModuleB, а ModuleB импортирует ModuleA, используйте forwardRef(). Но лучше пересмотреть архитектуру — циклические зависимости сигнализируют о неправильном разделении ответственности.

    // Костыль, а не решение
    @Module({
      imports: [forwardRef(() => OrderModule)],
    })
    export class UserModule {}
    

    Экспорт всех провайдеров. Когда модуль экспортирует все свои сервисы, репозитории и хелперы, он теряет инкапсуляцию. Экспортируйте только фасадный сервис.

    God Module. Один модуль, который импортирует и экспортирует всё подряд. Обычно это AppModule, который разрастается до сотен строк. Держите AppModule чистым — он только импортирует доменные модули.

    Заключение

    Правильная организация модулей в NestJS — это не про количество файлов и папок, а про четкие границы между доменами. Разделяйте код по бизнес-областям, экспортируйте минимум, используйте shared-модули для инфраструктуры и динамические модули для гибкой конфигурации. Такая модульная архитектура NestJS позволит масштабировать проект без превращения его в монолит, а при необходимости — легко выделить любой модуль в отдельный сервис.

    Иконка глаза425

    Комментарии

    0

    Постройте личный план изучения React state менеджер Zustand до уровня Middle — бесплатно!

    React state менеджер Zustand — часть карты развития Frontend

    • step100+ шагов развития
    • lessons30 бесплатных лекций
    • lessons300 бонусных рублей на счет

    Бесплатные лекции

    Лучшие курсы по теме

    изображение курса

    Vue 3 и Pinia

    Антон Ларичев
    AI-тренажерыAI-тренажеры
    Практика в студииПрактика в студии
    Гарантия
    Бонусы
    иконка звёздочки рейтинга4.9
    3 999 ₽ 6 990 ₽
    Подробнее
    изображение курса

    Next.js - с нуля

    Антон Ларичев
    AI-тренажерыAI-тренажеры
    Практика в студииПрактика в студии
    Гарантия
    Бонусы
    иконка звёздочки рейтинга4.7
    3 999 ₽ 6 990 ₽
    Подробнее
    изображение курса

    Feature-Sliced Design

    Антон Ларичев
    AI-тренажерыAI-тренажеры
    Практика в студииПрактика в студии
    Гарантия
    Бонусы
    иконка звёздочки рейтинга4.5
    3 999 ₽ 6 990 ₽
    Подробнее

    Похожие статьи

    Картинка поста CQRS и Event Sourcing на практике: когда это оправдано, а когда — оверинжиниринг
    Иконка аватараАнтон
    Иконка календаря09 апреля 2026
    architecturetypescriptnodejsseniorИконка уровня senior

    CQRS и Event Sourcing на практике: когда это оправдано, а когда — оверинжиниринг

    CQRS и Event Sourcing — архитектурные паттерны для сложных доменов. Разбираем реальные примеры на TypeScript и NestJS, критерии выбора и типичные ошибки внедрения.

    Иконка чипа0
    Иконка глаза301
    Иконка комментариев0
    Картинка поста MCP-серверы: как подключить AI-ассистента к вашему проекту через Model Context Protocol
    Иконка аватараАнтон
    Иконка календаря17 апреля 2026
    ainodejstypescriptmiddleИконка уровня middle

    MCP-серверы: как подключить AI-ассистента к вашему проекту через Model Context Protocol

    MCP сервер позволяет AI-ассистенту работать с данными и инструментами вашего проекта через Model Context Protocol. Разбираем архитектуру и создаём сервер на TypeScript с нуля.

    Иконка чипа0
    Иконка глаза351
    Иконка комментариев0
    Картинка поста Как внедрить тесты в проект, где их никогда не было: пошаговая стратегия
    Иконка аватараАнтон
    Иконка календаря14 апреля 2026
    testingjavascripttypescriptmiddleИконка уровня middle

    Как внедрить тесты в проект, где их никогда не было: пошаговая стратегия

    Пошаговая стратегия внедрения тестов в существующий проект: с чего начать тестирование legacy-кода, какие тесты писать первыми и как настроить Vitest для JavaScript и TypeScript.

    Иконка чипа0
    Иконка глаза266
    Иконка комментариев0
    Иконка чипа0