NestJSSeniorSystem design
Как структурировать крупное NestJS-приложение с несколькими доменами?
Крупное NestJS-приложение структурируется по доменам (feature modules), с явными границами через публичные API модулей, shared-библиотеками для переиспользуемого кода и разделением инфраструктурных и доменных слоёв.
Структура крупного NestJS-приложения
Доменное разбиение (Feature Modules)
Основной принцип: каждый бизнес-домен — отдельный модуль. Модули общаются через явно экспортированные сервисы, не через прямые импорты репозиториев чужих доменов.
src/
modules/
orders/
orders.module.ts
orders.controller.ts
orders.service.ts
orders.repository.ts
dto/
create-order.dto.ts
order-response.dto.ts
entities/
order.entity.ts
events/
order-created.event.ts
users/
users.module.ts
...
products/
products.module.ts
...
shared/
database/
database.module.ts
base.repository.ts
auth/
auth.module.ts
decorators/
current-user.decorator.ts
guards/
jwt-auth.guard.ts
common/
filters/
http-exception.filter.ts
interceptors/
logging.interceptor.ts
pipes/
validation.pipe.ts
config/
app.config.ts
database.config.ts
app.module.ts
main.ts
Пример модуля с явными границами
// orders.module.ts
@Module({
imports: [
TypeOrmModule.forFeature([Order, OrderItem]),
UsersModule, // импортируем только модуль, не репозиторий!
EventEmitterModule,
],
controllers: [OrdersController],
providers: [OrdersService, OrdersRepository],
exports: [OrdersService], // публичный API домена
})
export class OrdersModule {}
// users.module.ts — экспортирует только нужное
@Module({
providers: [UsersService, UsersRepository],
exports: [UsersService], // НЕ UsersRepository!
})
export class UsersModule {}
Shared Database Module
// shared/database/database.module.ts
@Module({
imports: [
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
useFactory: (config: ConfigService) => ({
type: 'postgres',
url: config.get('DATABASE_URL'),
entities: [__dirname + '/../../**/*.entity{.ts,.js}'],
migrations: [__dirname + '/../../migrations/*{.ts,.js}'],
synchronize: false,
}),
inject: [ConfigService],
}),
],
exports: [TypeOrmModule],
})
export class DatabaseModule {}
Nx Monorepo для нескольких сервисов
При наличии нескольких NestJS-сервисов (API + worker + notifications) используйте Nx workspace для переиспользования типов и утилит:
// libs/shared-types/src/index.ts — общие DTO
export interface UserDto {
id: string;
email: string;
role: UserRole;
}
// В приложениях
import { UserDto } from '@myapp/shared-types';
Конфигурация по доменам
// config/database.config.ts
import { registerAs } from '@nestjs/config';
export const databaseConfig = registerAs('db', () => ({
url: process.env.DATABASE_URL,
poolSize: parseInt(process.env.DB_POOL_SIZE ?? '10', 10),
}));
// config/app.config.ts
export const appConfig = registerAs('app', () => ({
port: parseInt(process.env.PORT ?? '3000', 10),
env: process.env.NODE_ENV ?? 'development',
}));
Подводные камни
- Circular dependency между Feature Modules — признак неверно определённых границ доменов; решается введением третьего модуля или Event-driven коммуникацией.
- Слишком много Global модулей размывает видимые зависимости и делает граф зависимостей непрозрачным; глобальными должны быть только ConfigModule, LoggerModule, DatabaseModule.
- Репозитории, экспортированные из модулей, нарушают инкапсуляцию домена — другие модули получают прямой доступ к данным в обход бизнес-логики.
- Отсутствие barrel-файлов (index.ts) в папках приводит к длинным относительным путям вида
../../modules/orders/dto/create-order.dto. - Смешение entities (TypeORM) и domain objects: entity — это деталь инфраструктуры, не должна протекать в контроллеры напрямую.
- Единственный AppModule импортирующий все Feature Modules теряет ленивую загрузку; рассмотрите Lazy-loaded modules для редких частей системы.
- При использовании Nx: версии NestJS в apps/ и libs/ должны быть синхронизированы, иначе DI-контейнер видит разные экземпляры одного класса.
Common mistakes
- Дает общий ответ про Node.js и не называет конкретные API NestJS.
- Не объясняет, где в lifecycle находится структура крупного доменного NestJS-приложения.
- Не разделяет validation, authorization, business logic и persistence.
- Игнорирует ошибки, лимиты входных данных, observability и тестирование.
What the interviewer is testing
- Может объяснить структура крупного доменного NestJS-приложения на примере кода.
- Называет ключевые API: domain modules, shared module.
- Использует точные API NestJS, а не вымышленные hooks/decorators/methods.
- Видит production-риски: безопасность, отказоустойчивость, логирование и тесты.