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

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

  • Курсы
    • FrontendИконка стрелки
    • AI разработкаИконка стрелки
    • BackendИконка стрелки
    • DevOpsИконка стрелки
    • MobileИконка стрелки
    • ТестированиеИконка стрелки
    • Soft-skillsИконка стрелки
    • ДизайнИконка стрелки
    Иконка слояПерейти в каталог курсов
  • Бесплатно
    • Курсы
    • JavaScript Основы разработкиPython Основы PythonCSS CSS FlexboxКарта развитияВопросы для собеседований
    • База знанийИконка стрелки
    • Новостные рассылкиИконка стрелки
  • PurpleSchool — курсы программирования онлайн
    • AI для кодаНовое
    • Сообщество
    • PurpleПлюс
    • AI Собеседование
    • 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 позволит масштабировать проект без превращения его в монолит, а при необходимости — легко выделить любой модуль в отдельный сервис.

    Иконка глаза1 081

    Комментарии

    0

    Постройте личный план изучения Основы React, React Router и Redux Toolkit до уровня Middle — бесплатно!

    Основы React, React Router и Redux Toolkit — часть карты развития Frontend, Mobile

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

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

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

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

    Zustand

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

    Next.js - с нуля

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

    Feature-Sliced Design

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

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

    Картинка поста TypeScript дженерики на практике: примеры и лучшие приёмы
    Иконка аватараАнтон
    Иконка календаря11 сентября 2026
    typescriptдженерикиgenerics+ 1middleИконка уровня middle

    TypeScript дженерики на практике: примеры и лучшие приёмы

    TypeScript дженерики на практике: как писать переиспользуемые типобезопасные функции, интерфейсы и хуки без потери контроля над типами.

    Иконка чипа0
    Иконка глаза207
    Иконка комментариев0
    Иконка аватараАнтон
    Иконка календаря24 сентября 2026
    Claude CodeAnthropicAI-инструменты+ 2middleИконка уровня middle

    Как установить Claude Code на Windows: инструкция из России

    Claude Code ставится на Windows одной командой `npm install -g @anthropic-ai/claude-code` — при условии, что в системе есть Node.js. Проблема начинается после установки: CLI поднимется, но при первом же запросе вернёт ошибку доступа, потому что API Anthropic не обслуживает запросы из России. Поэтому установка на Windows из России — это два отдельных дела: поставить сам пакет и подсунуть ему рабочий адрес API с ключом. Ниже — оба шага по порядку, с синтаксисом именно для PowerShell, а не для bash.

    Иконка чипа0
    Иконка глаза79
    Иконка комментариев0
    Картинка поста Микросервисная архитектура для начинающих: гайд с примерами
    Иконка аватараАнтон
    Иконка календаря23 сентября 2026
    микросервисыархитектураbackend+ 2juniorИконка уровня junior

    Микросервисная архитектура для начинающих: гайд с примерами

    Микросервисная архитектура простыми словами: чем она отличается от монолита, какие есть плюсы и минусы, и как спроектировать первую систему на микросервисах — с примерами кода.

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