7 Module in NestJS

Module sind das Herzstück der NestJS-Architektur und ermöglichen die Strukturierung von Anwendungen in logische, wiederverwendbare und testbare Einheiten. Sie organisieren verwandte Komponenten wie Controller, Services und Provider in kohärente Funktionsgruppen und definieren klare Grenzen zwischen verschiedenen Teilen der Anwendung.

7.1 Hands-On Einstieg in NestJS-Module

7.1.1 Das erste Modul erstellen

Beginnen wir mit der Erstellung eines einfachen Moduls, um die Grundkonzepte zu verstehen. Ein Modul in NestJS ist eine TypeScript-Klasse, die mit dem @Module()-Dekorator annotiert ist.

7.1.1.1 Grundlegendes Modul-Setup

# Neues Modul über die CLI erstellen
nest generate module users
# oder kurz:
nest g module users

Dies generiert folgende Datei:

// src/users/users.module.ts
import { Module } from '@nestjs/common';

@Module({})
export class UsersModule {}

Ein leeres Modul ist jedoch noch nicht sehr nützlich. Lassen Sie uns einen Controller und Service hinzufügen:

# Controller und Service für das Users-Modul erstellen
nest g controller users
nest g service users

Nach der Generierung sieht unser Modul folgendermaßen aus:

// src/users/users.module.ts
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';

@Module({
  controllers: [UsersController],
  providers: [UsersService],
})
export class UsersModule {}

7.1.1.2 Das Modul ins Root-Modul integrieren

Damit unser neues Modul von der Anwendung erkannt wird, muss es in das Root-Modul importiert werden:

// src/app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UsersModule } from './users/users.module';

@Module({
  imports: [UsersModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

7.1.2 Die Anatomie eines Moduls verstehen

Der @Module()-Dekorator akzeptiert ein Konfigurationsobjekt mit verschiedenen Eigenschaften, die das Verhalten des Moduls definieren.

7.1.2.1 Module-Metadaten im Detail

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
import { UserRepository } from './user.repository';
import { User } from './entities/user.entity';
import { EmailService } from '../shared/services/email.service';

@Module({
  // Module, die von diesem Modul benötigt werden
  imports: [
    TypeOrmModule.forFeature([User]),
  ],
  
  // Controller, die in diesem Modul definiert sind
  controllers: [UsersController],
  
  // Provider (Services, Repositories, etc.), die in diesem Modul verfügbar sind
  providers: [
    UsersService,
    UserRepository,
    {
      provide: 'EMAIL_SERVICE',
      useClass: EmailService,
    },
  ],
  
  // Provider, die für andere Module verfügbar gemacht werden
  exports: [
    UsersService,
    UserRepository,
  ],
})
export class UsersModule {}

Eigenschaften des @Module() Dekorators:

7.1.2.2 Erweiterte Provider-Konfiguration

Provider können auf verschiedene Weise definiert werden:

@Module({
  providers: [
    // Kurzform - Klasse als Token und Implementierung
    UsersService,
    
    // Vollständige Form
    {
      provide: UsersService,
      useClass: UsersService,
    },
    
    // Factory Provider
    {
      provide: 'DATABASE_CONNECTION',
      useFactory: async (configService: ConfigService) => {
        const config = configService.get('database');
        return createConnection(config);
      },
      inject: [ConfigService],
    },
    
    // Value Provider
    {
      provide: 'API_VERSION',
      useValue: 'v1',
    },
    
    // Async Provider
    {
      provide: 'ASYNC_SERVICE',
      useFactory: async () => {
        const connection = await createConnection();
        return new AsyncService(connection);
      },
    },
  ],
})
export class UsersModule {}

7.1.3 Modulare Architektur und Abhängigkeiten

NestJS-Module können voneinander abhängen und eine komplexe Hierarchie bilden. Das Verständnis dieser Abhängigkeiten ist entscheidend für eine saubere Architektur.

7.1.3.1 Module-Dependency-Graph

// Shared Module - wird von vielen anderen Modulen verwendet
@Module({
  providers: [LoggerService, ConfigService],
  exports: [LoggerService, ConfigService],
})
export class SharedModule {}

// Auth Module - abhängig von Users und Shared
@Module({
  imports: [UsersModule, SharedModule],
  providers: [AuthService, JwtService],
  controllers: [AuthController],
  exports: [AuthService],
})
export class AuthModule {}

// Users Module - abhängig von Shared
@Module({
  imports: [SharedModule],
  providers: [UsersService],
  controllers: [UsersController],
  exports: [UsersService],
})
export class UsersModule {}

// Products Module - abhängig von Auth (für Authentifizierung)
@Module({
  imports: [AuthModule, SharedModule],
  providers: [ProductsService],
  controllers: [ProductsController],
})
export class ProductsModule {}

7.1.3.2 Circular Dependencies vermeiden

Zirkuläre Abhängigkeiten zwischen Modulen können auftreten und sollten vermieden werden:

// Problematisch: Users importiert Auth, Auth importiert Users
// Lösung 1: Gemeinsame Funktionalität in separates Modul auslagern

@Module({
  providers: [SharedUserService],
  exports: [SharedUserService],
})
export class SharedUserModule {}

@Module({
  imports: [SharedUserModule],
  providers: [AuthService],
  exports: [AuthService],
})
export class AuthModule {}

@Module({
  imports: [SharedUserModule],
  providers: [UsersService],
  exports: [UsersService],
})
export class UsersModule {}

// Lösung 2: forwardRef() verwenden (nur wenn unvermeidlich)
@Module({
  imports: [forwardRef(() => AuthModule)],
  providers: [UsersService],
  exports: [UsersService],
})
export class UsersModule {}

7.1.4 Warum Module?

Module bieten mehrere entscheidende Vorteile für die Anwendungsarchitektur:

7.1.4.1 Kapselung und Isolation

Module schaffen klare Grenzen zwischen verschiedenen Funktionsbereichen:

// Jedes Modul kapselt seine Logik
@Module({
  providers: [
    UserService,      // Nur innerhalb UsersModule verfügbar
    UserRepository,   // Nur innerhalb UsersModule verfügbar
  ],
  exports: [UserService], // Nur UserService ist für andere Module verfügbar
})
export class UsersModule {}

7.1.4.2 Wiederverwendbarkeit

Module können in verschiedenen Kontexten wiederverwendet werden:

// Logging-Modul kann in allen Anwendungen verwendet werden
@Module({
  providers: [
    {
      provide: 'LOGGER',
      useFactory: (config: ConfigService) => {
        return new Logger(config.get('logLevel'));
      },
      inject: [ConfigService],
    },
  ],
  exports: ['LOGGER'],
})
export class LoggingModule {
  static forRoot(options: LoggingOptions): DynamicModule {
    return {
      module: LoggingModule,
      providers: [
        {
          provide: 'LOGGING_OPTIONS',
          useValue: options,
        },
      ],
    };
  }
}

7.1.4.3 Testbarkeit

Module erleichtern das Testen durch Isolation von Abhängigkeiten:

// Test-Setup für UsersModule
describe('UsersModule', () => {
  let module: TestingModule;

  beforeEach(async () => {
    module = await Test.createTestingModule({
      imports: [UsersModule],
    })
    .overrideProvider(UserRepository)
    .useValue(mockUserRepository)
    .compile();
  });

  it('should be defined', () => {
    expect(module).toBeDefined();
  });
});

7.1.4.4 Lazy Loading (für Microservices)

Module können bei Bedarf geladen werden:

// Dynamisches Laden von Modulen
async function loadUserModule() {
  const { UsersModule } = await import('./users/users.module');
  return UsersModule;
}

7.1.5 Das große Ganze

Module in NestJS folgen einem hierarchischen Muster, das der Anwendungsarchitektur entspricht:

AppModule (Root)
├── CoreModule (Singleton Services)
│   ├── ConfigModule
│   ├── DatabaseModule
│   └── LoggingModule
├── SharedModule (Wiederverwendbare Services)
│   ├── UtilsModule
│   ├── ValidationModule
│   └── CacheModule
└── FeatureModules (Geschäftslogik)
    ├── AuthModule
    ├── UsersModule
    ├── ProductsModule
    └── OrdersModule

7.1.5.1 Root Module Konfiguration

// src/app.module.ts
@Module({
  imports: [
    // Core Module - nur einmal importiert
    CoreModule,
    
    // Feature Module
    AuthModule,
    UsersModule,
    ProductsModule,
    OrdersModule,
    
    // Conditional Imports basierend auf Environment
    ...(process.env.NODE_ENV === 'development' ? [DevToolsModule] : []),
  ],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes('*');
  }
}

7.2 Fortgeschrittene Modul-Konzepte

7.2.1 Feature-Module

Feature-Module organisieren geschäftsspezifische Funktionalitäten und sind die häufigste Art von Modulen in NestJS-Anwendungen.

7.2.1.1 Vollständiges Feature-Modul

// src/products/products.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ProductsController } from './controllers/products.controller';
import { ProductsService } from './services/products.service';
import { ProductRepository } from './repositories/product.repository';
import { Product } from './entities/product.entity';
import { Category } from './entities/category.entity';
import { ProductsResolver } from './resolvers/products.resolver';
import { CacheModule } from '@nestjs/cache-manager';

@Module({
  imports: [
    TypeOrmModule.forFeature([Product, Category]),
    CacheModule.register({
      ttl: 300, // 5 Minuten
      max: 100, // Maximale Anzahl von Einträgen
    }),
  ],
  controllers: [ProductsController],
  providers: [
    ProductsService,
    ProductRepository,
    ProductsResolver,
    {
      provide: 'PRODUCT_CONFIG',
      useValue: {
        maxPrice: 10000,
        defaultCurrency: 'EUR',
      },
    },
  ],
  exports: [ProductsService, ProductRepository],
})
export class ProductsModule {}

7.2.1.2 Feature-Modul mit Subdomain-Struktur

// src/products/products.module.ts
@Module({
  imports: [
    // Sub-Module für verschiedene Aspekte
    ProductCatalogModule,
    ProductInventoryModule,
    ProductPricingModule,
    ProductReviewsModule,
  ],
  exports: [
    ProductCatalogModule,
    ProductInventoryModule,
    ProductPricingModule,
  ],
})
export class ProductsModule {}

// src/products/catalog/product-catalog.module.ts
@Module({
  providers: [ProductCatalogService],
  controllers: [ProductCatalogController],
  exports: [ProductCatalogService],
})
export class ProductCatalogModule {}

7.2.2 Shared-Module

Shared-Module enthalten wiederverwendbare Funktionalitäten, die von mehreren Feature-Modulen genutzt werden.

7.2.2.1 Global Shared Module

// src/shared/shared.module.ts
import { Global, Module } from '@nestjs/common';
import { EmailService } from './services/email.service';
import { FileUploadService } from './services/file-upload.service';
import { ValidationService } from './services/validation.service';
import { UtilsService } from './services/utils.service';

@Global() // Macht das Modul global verfügbar
@Module({
  providers: [
    EmailService,
    FileUploadService,
    ValidationService,
    UtilsService,
  ],
  exports: [
    EmailService,
    FileUploadService,
    ValidationService,
    UtilsService,
  ],
})
export class SharedModule {}

7.2.2.2 Conditional Shared Module

// src/shared/shared.module.ts
@Module({})
export class SharedModule {
  static forRoot(options: SharedModuleOptions): DynamicModule {
    const providers = [UtilsService];
    
    if (options.enableEmail) {
      providers.push(EmailService);
    }
    
    if (options.enableFileUpload) {
      providers.push(FileUploadService);
    }
    
    return {
      module: SharedModule,
      providers,
      exports: providers,
      global: options.global ?? false,
    };
  }
}

// Verwendung im AppModule
@Module({
  imports: [
    SharedModule.forRoot({
      enableEmail: true,
      enableFileUpload: true,
      global: true,
    }),
  ],
})
export class AppModule {}

7.2.3 Core-Module

Core-Module enthalten Singleton-Services, die Application-weit verfügbar sein sollen und nur einmal instanziiert werden.

7.2.3.1 Singleton Core Module

// src/core/core.module.ts
import { Global, Module, OnModuleInit } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { DatabaseService } from './services/database.service';
import { LoggerService } from './services/logger.service';
import { HealthService } from './services/health.service';

@Global()
@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      envFilePath: ['.env.local', '.env'],
    }),
    TypeOrmModule.forRootAsync({
      useFactory: (configService: ConfigService) => ({
        type: 'postgres',
        host: configService.get('DB_HOST'),
        port: configService.get('DB_PORT'),
        username: configService.get('DB_USERNAME'),
        password: configService.get('DB_PASSWORD'),
        database: configService.get('DB_NAME'),
        autoLoadEntities: true,
        synchronize: configService.get('NODE_ENV') !== 'production',
      }),
      inject: [ConfigService],
    }),
  ],
  providers: [DatabaseService, LoggerService, HealthService],
  exports: [DatabaseService, LoggerService, HealthService],
})
export class CoreModule implements OnModuleInit {
  constructor(
    private readonly databaseService: DatabaseService,
    private readonly logger: LoggerService,
  ) {}

  async onModuleInit() {
    await this.databaseService.checkConnection();
    this.logger.log('Core module initialized', 'CoreModule');
  }
}

7.2.3.2 Core Module mit Guards

// src/core/core.module.ts
@Module({
  providers: [
    // Nur im Root Module importieren
    {
      provide: 'CORE_GUARD',
      useFactory: () => {
        if (CoreModule.initialized) {
          throw new Error('CoreModule is already loaded. Import it only once in AppModule.');
        }
        CoreModule.initialized = true;
        return true;
      },
    },
  ],
})
export class CoreModule {
  private static initialized = false;
  
  constructor(@Optional() @SkipSelf() parentModule: CoreModule) {
    if (parentModule) {
      throw new Error('CoreModule is already loaded. Import it only once in AppModule.');
    }
  }
}

7.2.4 Dynamische Module

Dynamische Module ermöglichen die Konfiguration von Modulen zur Laufzeit und sind besonders nützlich für wiederverwendbare Bibliotheken.

7.2.4.1 Einfaches dynamisches Modul

// src/database/database.module.ts
import { DynamicModule, Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';

export interface DatabaseModuleOptions {
  host: string;
  port: number;
  username: string;
  password: string;
  database: string;
  entities: any[];
}

@Module({})
export class DatabaseModule {
  static forRoot(options: DatabaseModuleOptions): DynamicModule {
    return {
      module: DatabaseModule,
      imports: [
        TypeOrmModule.forRoot({
          type: 'postgres',
          ...options,
        }),
      ],
      global: true,
    };
  }
  
  static forFeature(entities: any[]): DynamicModule {
    return {
      module: DatabaseModule,
      imports: [TypeOrmModule.forFeature(entities)],
      exports: [TypeOrmModule],
    };
  }
}

7.2.4.2 Async Dynamic Module

// src/config/config.module.ts
@Module({})
export class ConfigModule {
  static forRootAsync(options: ConfigAsyncOptions): DynamicModule {
    return {
      module: ConfigModule,
      imports: options.imports || [],
      providers: [
        {
          provide: 'CONFIG_OPTIONS',
          useFactory: options.useFactory,
          inject: options.inject || [],
        },
        {
          provide: 'CONFIG_SERVICE',
          useFactory: async (configOptions: any) => {
            const config = await loadConfig(configOptions);
            return new ConfigService(config);
          },
          inject: ['CONFIG_OPTIONS'],
        },
      ],
      exports: ['CONFIG_SERVICE'],
      global: options.isGlobal ?? false,
    };
  }
}

// Verwendung
@Module({
  imports: [
    ConfigModule.forRootAsync({
      imports: [HttpModule],
      useFactory: async (httpService: HttpService) => {
        const remoteConfig = await httpService.get('/config').toPromise();
        return remoteConfig.data;
      },
      inject: [HttpService],
      isGlobal: true,
    }),
  ],
})
export class AppModule {}

7.2.4.3 Advanced Dynamic Module Pattern

// src/auth/auth.module.ts
export interface AuthModuleOptions {
  jwtSecret: string;
  jwtExpiresIn: string;
  enableRefreshTokens?: boolean;
  providers?: ('local' | 'google' | 'facebook')[];
}

@Module({})
export class AuthModule {
  static forRoot(options: AuthModuleOptions): DynamicModule {
    const providers = [
      AuthService,
      JwtStrategy,
      {
        provide: 'AUTH_OPTIONS',
        useValue: options,
      },
    ];

    // Conditional providers based on options
    if (options.enableRefreshTokens) {
      providers.push(RefreshTokenService);
    }

    if (options.providers?.includes('google')) {
      providers.push(GoogleStrategy);
    }

    if (options.providers?.includes('facebook')) {
      providers.push(FacebookStrategy);
    }

    return {
      module: AuthModule,
      imports: [
        JwtModule.register({
          secret: options.jwtSecret,
          signOptions: { expiresIn: options.jwtExpiresIn },
        }),
      ],
      providers,
      controllers: [AuthController],
      exports: [AuthService],
    };
  }

  static forFeature(): DynamicModule {
    return {
      module: AuthModule,
      providers: [AuthGuard],
      exports: [AuthGuard],
    };
  }
}

7.3 Modul-Design und Best Practices

7.3.1 Modul-Hierarchie und -Struktur

Eine durchdachte Modul-Hierarchie ist entscheidend für die Wartbarkeit und Skalierbarkeit von NestJS-Anwendungen.

7.3.1.1 Empfohlene Modul-Hierarchie

AppModule (Root)
├── CoreModule (Infrastructure & Singletons)
│   ├── ConfigModule
│   ├── DatabaseModule  
│   ├── LoggingModule
│   └── HealthModule
│
├── SharedModule (Common Utilities)
│   ├── ValidationModule
│   ├── CacheModule
│   ├── EmailModule
│   └── FileUploadModule
│
├── FeatureModules (Business Logic)
│   ├── AuthModule
│   │   ├── GuardsModule
│   │   ├── StrategiesModule
│   │   └── TokenModule
│   │
│   ├── UsersModule
│   │   ├── ProfileModule
│   │   ├── PreferencesModule
│   │   └── RolesModule
│   │
│   ├── ProductsModule
│   │   ├── CatalogModule
│   │   ├── InventoryModule
│   │   ├── PricingModule
│   │   └── ReviewsModule
│   │
│   └── OrdersModule
│       ├── PaymentModule
│       ├── ShippingModule
│       └── InvoiceModule
│
└── IntegrationModules (External Services)
    ├── PaymentGatewayModule
    ├── NotificationModule
    └── AnalyticsModule

7.3.1.2 Module Layer Pattern

// Layer 1: Infrastructure
@Module({
  imports: [ConfigModule, DatabaseModule],
  exports: [ConfigModule, DatabaseModule],
})
export class InfrastructureModule {}

// Layer 2: Domain Services
@Module({
  imports: [InfrastructureModule],
  providers: [UserDomainService, ProductDomainService],
  exports: [UserDomainService, ProductDomainService],
})
export class DomainModule {}

// Layer 3: Application Services
@Module({
  imports: [DomainModule],
  providers: [UserApplicationService, ProductApplicationService],
  exports: [UserApplicationService, ProductApplicationService],
})
export class ApplicationModule {}

// Layer 4: Presentation
@Module({
  imports: [ApplicationModule],
  controllers: [UsersController, ProductsController],
})
export class PresentationModule {}

7.3.2 Beispiel einer typischen Modul-Struktur

7.3.2.1 Feature-Modul Struktur

src/users/
├── controllers/
│   ├── users.controller.ts
│   ├── user-profile.controller.ts
│   └── user-admin.controller.ts
├── services/
│   ├── users.service.ts
│   ├── user-profile.service.ts
│   └── user-validation.service.ts
├── repositories/
│   ├── user.repository.ts
│   └── user-profile.repository.ts
├── dto/
│   ├── create-user.dto.ts
│   ├── update-user.dto.ts
│   ├── user-profile.dto.ts
│   └── user-query.dto.ts
├── entities/
│   ├── user.entity.ts
│   └── user-profile.entity.ts
├── interfaces/
│   ├── user.interface.ts
│   └── user-repository.interface.ts
├── guards/
│   └── user-ownership.guard.ts
├── pipes/
│   └── user-validation.pipe.ts
├── decorators/
│   └── current-user.decorator.ts
├── tests/
│   ├── users.service.spec.ts
│   ├── users.controller.spec.ts
│   └── user.repository.spec.ts
└── users.module.ts

7.3.2.2 Komplettes Feature-Modul Beispiel

// src/users/users.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { CacheModule } from '@nestjs/cache-manager';

// Controllers
import { UsersController } from './controllers/users.controller';
import { UserProfileController } from './controllers/user-profile.controller';
import { UserAdminController } from './controllers/user-admin.controller';

// Services
import { UsersService } from './services/users.service';
import { UserProfileService } from './services/user-profile.service';
import { UserValidationService } from './services/user-validation.service';

// Repositories
import { UserRepository } from './repositories/user.repository';
import { UserProfileRepository } from './repositories/user-profile.repository';

// Entities
import { User } from './entities/user.entity';
import { UserProfile } from './entities/user-profile.entity';

// Guards
import { UserOwnershipGuard } from './guards/user-ownership.guard';

// External Dependencies
import { AuthModule } from '../auth/auth.module';
import { SharedModule } from '../shared/shared.module';

@Module({
  imports: [
    TypeOrmModule.forFeature([User, UserProfile]),
    CacheModule.register({
      ttl: 300,
      max: 1000,
    }),
    AuthModule.forFeature(), // Importiert nur AuthGuard
    SharedModule, // Globales Modul
  ],
  controllers: [
    UsersController,
    UserProfileController,
    UserAdminController,
  ],
  providers: [
    // Services
    UsersService,
    UserProfileService,
    UserValidationService,
    
    // Repositories
    UserRepository,
    UserProfileRepository,
    
    // Guards
    UserOwnershipGuard,
    
    // Custom Providers
    {
      provide: 'USER_CONFIG',
      useValue: {
        maxProfileImageSize: 2 * 1024 * 1024, // 2MB
        allowedImageTypes: ['jpg', 'jpeg', 'png'],
      },
    },
    
    // Factory Provider
    {
      provide: 'USER_CACHE_KEY_GENERATOR',
      useFactory: (configService: ConfigService) => {
        const prefix = configService.get('CACHE_PREFIX');
        return (userId: string) => `${prefix}:user:${userId}`;
      },
      inject: [ConfigService],
    },
  ],
  exports: [
    UsersService,
    UserRepository,
    UserValidationService,
  ],
})
export class UsersModule {}

7.3.3 Import und Export von Modulen

Das Verständnis von Import- und Export-Mechanismen ist entscheidend für eine saubere Modul-Architektur.

7.3.3.1 Import-Strategien

// Standard Import
@Module({
  imports: [UsersModule],
})
export class AppModule {}

// Conditional Import
@Module({
  imports: [
    UsersModule,
    ...(process.env.NODE_ENV === 'development' ? [DevToolsModule] : []),
    ...(process.env.ENABLE_ADMIN === 'true' ? [AdminModule] : []),
  ],
})
export class AppModule {}

// Dynamic Import mit Konfiguration
@Module({
  imports: [
    DatabaseModule.forRoot({
      type: 'postgres',
      host: process.env.DB_HOST,
    }),
    CacheModule.forRoot({
      store: 'redis',
      host: process.env.REDIS_HOST,
    }),
  ],
})
export class AppModule {}

7.3.3.2 Export-Patterns

// Selective Export
@Module({
  providers: [
    UserService,
    UserRepository,
    UserValidationService,
    InternalUserService, // Nicht exportiert
  ],
  exports: [
    UserService,
    UserRepository,
    // UserValidationService nicht exportiert
  ],
})
export class UsersModule {}

// Re-Export von importierten Modulen
@Module({
  imports: [TypeOrmModule.forFeature([User])],
  providers: [UserService],
  exports: [
    UserService,
    TypeOrmModule, // Re-export für andere Module
  ],
})
export class UsersModule {}

// Token-basierter Export
@Module({
  providers: [
    {
      provide: 'USER_SERVICE',
      useClass: UserService,
    },
  ],
  exports: ['USER_SERVICE'],
})
export class UsersModule {}

7.3.3.3 Module-Komposition

// Aggregator Module Pattern
@Module({
  imports: [
    UserCoreModule,
    UserProfileModule,
    UserPreferencesModule,
    UserRolesModule,
  ],
  exports: [
    UserCoreModule,
    UserProfileModule,
    UserPreferencesModule,
    UserRolesModule,
  ],
})
export class UsersModule {}

// Facade Module Pattern
@Module({
  imports: [UsersModule, ProductsModule, OrdersModule],
  providers: [ECommerceService], // Vereint Funktionalitäten
  exports: [ECommerceService],
})
export class ECommerceModule {}

7.4 Praxisbeispiel - Ein komplettes Feature-Modul

Lassen Sie uns ein vollständiges Feature-Modul für ein Blog-System entwickeln, das alle besprochenen Konzepte demonstriert.

7.4.1 Blog Feature Modul

7.4.1.1 Verzeichnisstruktur

src/blog/
├── controllers/
│   ├── posts.controller.ts
│   ├── comments.controller.ts
│   └── categories.controller.ts
├── services/
│   ├── posts.service.ts
│   ├── comments.service.ts
│   ├── categories.service.ts
│   └── blog-search.service.ts
├── repositories/
│   ├── post.repository.ts
│   ├── comment.repository.ts
│   └── category.repository.ts
├── dto/
│   ├── create-post.dto.ts
│   ├── update-post.dto.ts
│   ├── create-comment.dto.ts
│   ├── post-query.dto.ts
│   └── pagination.dto.ts
├── entities/
│   ├── post.entity.ts
│   ├── comment.entity.ts
│   └── category.entity.ts
├── guards/
│   ├── post-ownership.guard.ts
│   └── post-published.guard.ts
├── interceptors/
│   └── post-cache.interceptor.ts
├── pipes/
│   └── post-validation.pipe.ts
├── interfaces/
│   ├── blog.interface.ts
│   └── search-result.interface.ts
└── blog.module.ts

7.4.1.2 Entity-Definitionen

// src/blog/entities/post.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, OneToMany, CreateDateColumn, UpdateDateColumn } from 'typeorm';
import { User } from '../../users/entities/user.entity';
import { Category } from './category.entity';
import { Comment } from './comment.entity';

@Entity('posts')
export class Post {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column()
  title: string;

  @Column()
  slug: string;

  @Column('text')
  content: string;

  @Column('text', { nullable: true })
  excerpt: string;

  @Column({ default: false })
  published: boolean;

  @Column({ name: 'featured_image', nullable: true })
  featuredImage: string;

  @Column('simple-array', { nullable: true })
  tags: string[];

  @ManyToOne(() => User, user => user.posts)
  author: User;

  @ManyToOne(() => Category, category => category.posts)
  category: Category;

  @OneToMany(() => Comment, comment => comment.post)
  comments: Comment[];

  @CreateDateColumn({ name: 'created_at' })
  createdAt: Date;

  @UpdateDateColumn({ name: 'updated_at' })
  updatedAt: Date;
}

// src/blog/entities/comment.entity.ts
@Entity('comments')
export class Comment {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column('text')
  content: string;

  @Column({ default: false })
  approved: boolean;

  @ManyToOne(() => User, user => user.comments)
  author: User;

  @ManyToOne(() => Post, post => post.comments, { onDelete: 'CASCADE' })
  post: Post;

  @ManyToOne(() => Comment, comment => comment.replies, { nullable: true })
  parent: Comment;

  @OneToMany(() => Comment, comment => comment.parent)
  replies: Comment[];

  @CreateDateColumn({ name: 'created_at' })
  createdAt: Date;
}

// src/blog/entities/category.entity.ts
@Entity('categories')
export class Category {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column({ unique: true })
  name: string;

  @Column({ unique: true })
  slug: string;

  @Column('text', { nullable: true })
  description: string;

  @OneToMany(() => Post, post => post.category)
  posts: Post[];
}

7.4.1.3 DTOs

// src/blog/dto/create-post.dto.ts
import { IsString, IsBoolean, IsOptional, IsArray, IsUUID, MinLength, MaxLength } from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';

export class CreatePostDto {
  @ApiProperty({ description: 'Post title', minLength: 5, maxLength: 100 })
  @IsString()
  @MinLength(5)
  @MaxLength(100)
  title: string;

  @ApiProperty({ description: 'Post content' })
  @IsString()
  @MinLength(10)
  content: string;

  @ApiPropertyOptional({ description: 'Post excerpt' })
  @IsOptional()
  @IsString()
  @MaxLength(255)
  excerpt?: string;

  @ApiPropertyOptional({ description: 'Publication status', default: false })
  @IsOptional()
  @IsBoolean()
  published?: boolean;

  @ApiPropertyOptional({ description: 'Featured image URL' })
  @IsOptional()
  @IsString()
  featuredImage?: string;

  @ApiPropertyOptional({ description: 'Post tags' })
  @IsOptional()
  @IsArray()
  @IsString({ each: true })
  tags?: string[];

  @ApiProperty({ description: 'Category ID' })
  @IsUUID()
  categoryId: string;
}

// src/blog/dto/post-query.dto.ts
import { IsOptional, IsString, IsBoolean, IsInt, Min, Max } from 'class-validator';
import { Type } from 'class-transformer';
import { ApiPropertyOptional } from '@nestjs/swagger';

export class PostQueryDto {
  @ApiPropertyOptional({ description: 'Search term' })
  @IsOptional()
  @IsString()
  search?: string;

  @ApiPropertyOptional({ description: 'Category slug' })
  @IsOptional()
  @IsString()
  category?: string;

  @ApiPropertyOptional({ description: 'Tag filter' })
  @IsOptional()
  @IsString()
  tag?: string;

  @ApiPropertyOptional({ description: 'Author ID' })
  @IsOptional()
  @IsString()
  author?: string;

  @ApiPropertyOptional({ description: 'Publication status' })
  @IsOptional()
  @IsBoolean()
  @Type(() => Boolean)
  published?: boolean;

  @ApiPropertyOptional({ description: 'Page number', minimum: 1, default: 1 })
  @IsOptional()
  @IsInt()
  @Min(1)
  @Type(() => Number)
  page?: number = 1;

  @ApiPropertyOptional({ description: 'Items per page', minimum: 1, maximum: 100, default: 10 })
  @IsOptional()
  @IsInt()
  @Min(1)
  @Max(100)
  @Type(() => Number)
  limit?: number = 10;
}

7.4.1.4 Services

// src/blog/services/posts.service.ts
import { Injectable, NotFoundException, ForbiddenException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Post } from '../entities/post.entity';
import { CreatePostDto } from '../dto/create-post.dto';
import { UpdatePostDto } from '../dto/update-post.dto';
import { PostQueryDto } from '../dto/post-query.dto';
import { User } from '../../users/entities/user.entity';
import { BlogSearchService } from './blog-search.service';
import { CacheService } from '../../shared/services/cache.service';

@Injectable()
export class PostsService {
  constructor(
    @InjectRepository(Post)
    private readonly postRepository: Repository<Post>,
    private readonly searchService: BlogSearchService,
    private readonly cacheService: CacheService,
  ) {}

  async create(createPostDto: CreatePostDto, author: User): Promise<Post> {
    const slug = this.generateSlug(createPostDto.title);
    
    const post = this.postRepository.create({
      ...createPostDto,
      slug,
      author,
    });

    const savedPost = await this.postRepository.save(post);
    
    // Index for search
    await this.searchService.indexPost(savedPost);
    
    // Invalidate relevant caches
    await this.cacheService.del(`posts:category:${createPostDto.categoryId}`);
    
    return savedPost;
  }

  async findAll(queryDto: PostQueryDto): Promise<{ posts: Post[]; total: number }> {
    const cacheKey = `posts:query:${JSON.stringify(queryDto)}`;
    const cached = await this.cacheService.get(cacheKey);
    
    if (cached) {
      return cached;
    }

    const queryBuilder = this.postRepository
      .createQueryBuilder('post')
      .leftJoinAndSelect('post.author', 'author')
      .leftJoinAndSelect('post.category', 'category')
      .leftJoinAndSelect('post.comments', 'comments');

    // Apply filters
    if (queryDto.published !== undefined) {
      queryBuilder.andWhere('post.published = :published', { published: queryDto.published });
    }

    if (queryDto.category) {
      queryBuilder.andWhere('category.slug = :category', { category: queryDto.category });
    }

    if (queryDto.tag) {
      queryBuilder.andWhere(':tag = ANY(post.tags)', { tag: queryDto.tag });
    }

    if (queryDto.author) {
      queryBuilder.andWhere('author.id = :author', { author: queryDto.author });
    }

    if (queryDto.search) {
      queryBuilder.andWhere(
        '(post.title ILIKE :search OR post.content ILIKE :search OR post.excerpt ILIKE :search)',
        { search: `%${queryDto.search}%` }
      );
    }

    // Pagination
    const skip = (queryDto.page - 1) * queryDto.limit;
    queryBuilder.skip(skip).take(queryDto.limit);

    // Ordering
    queryBuilder.orderBy('post.createdAt', 'DESC');

    const [posts, total] = await queryBuilder.getManyAndCount();

    const result = { posts, total };
    
    // Cache for 5 minutes
    await this.cacheService.set(cacheKey, result, 300);
    
    return result;
  }

  async findOne(id: string): Promise<Post> {
    const cacheKey = `post:${id}`;
    const cached = await this.cacheService.get(cacheKey);
    
    if (cached) {
      return cached;
    }

    const post = await this.postRepository.findOne({
      where: { id },
      relations: ['author', 'category', 'comments', 'comments.author'],
    });

    if (!post) {
      throw new NotFoundException(`Post with ID ${id} not found`);
    }

    await this.cacheService.set(cacheKey, post, 600); // 10 minutes
    
    return post;
  }

  async update(id: string, updatePostDto: UpdatePostDto, user: User): Promise<Post> {
    const post = await this.findOne(id);
    
    if (post.author.id !== user.id && !user.roles.includes('admin')) {
      throw new ForbiddenException('You can only update your own posts');
    }

    if (updatePostDto.title && updatePostDto.title !== post.title) {
      updatePostDto.slug = this.generateSlug(updatePostDto.title);
    }

    Object.assign(post, updatePostDto);
    const updatedPost = await this.postRepository.save(post);
    
    // Update search index
    await this.searchService.updatePost(updatedPost);
    
    // Invalidate caches
    await this.cacheService.del(`post:${id}`);
    await this.cacheService.del(`posts:category:${post.category.id}`);
    
    return updatedPost;
  }

  async remove(id: string, user: User): Promise<void> {
    const post = await this.findOne(id);
    
    if (post.author.id !== user.id && !user.roles.includes('admin')) {
      throw new ForbiddenException('You can only delete your own posts');
    }

    await this.postRepository.remove(post);
    
    // Remove from search index
    await this.searchService.removePost(id);
    
    // Invalidate caches
    await this.cacheService.del(`post:${id}`);
    await this.cacheService.del(`posts:category:${post.category.id}`);
  }

  private generateSlug(title: string): string {
    return title
      .toLowerCase()
      .replace(/[^a-z0-9\s-]/g, '')
      .replace(/\s+/g, '-')
      .replace(/-+/g, '-')
      .trim();
  }
}

7.4.1.5 Module-Definition

// src/blog/blog.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { CacheModule } from '@nestjs/cache-manager';

// Controllers
import { PostsController } from './controllers/posts.controller';
import { CommentsController } from './controllers/comments.controller';
import { CategoriesController } from './controllers/categories.controller';

// Services
import { PostsService } from './services/posts.service';
import { CommentsService } from './services/comments.service';
import { CategoriesService } from './services/categories.service';
import { BlogSearchService } from './services/blog-search.service';

// Entities
import { Post } from './entities/post.entity';
import { Comment } from './entities/comment.entity';
import { Category } from './entities/category.entity';

// Guards
import { PostOwnershipGuard } from './guards/post-ownership.guard';
import { PostPublishedGuard } from './guards/post-published.guard';

// External modules
import { UsersModule } from '../users/users.module';
import { AuthModule } from '../auth/auth.module';
import { SharedModule } from '../shared/shared.module';

@Module({
  imports: [
    TypeOrmModule.forFeature([Post, Comment, Category]),
    CacheModule.register({
      ttl: 300, // 5 minutes default
      max: 1000,
    }),
    UsersModule, // For User entity relationships
    AuthModule.forFeature(), // For authentication guards
    SharedModule, // For shared services like CacheService
  ],
  controllers: [
    PostsController,
    CommentsController,
    CategoriesController,
  ],
  providers: [
    // Services
    PostsService,
    CommentsService,
    CategoriesService,
    BlogSearchService,
    
    // Guards
    PostOwnershipGuard,
    PostPublishedGuard,
    
    // Configuration
    {
      provide: 'BLOG_CONFIG',
      useValue: {
        postsPerPage: 10,
        maxPostLength: 50000,
        allowedImageTypes: ['jpg', 'jpeg', 'png', 'webp'],
        enableComments: true,
        enableSearch: true,
      },
    },
  ],
  exports: [
    PostsService,
    CategoriesService,
    BlogSearchService,
  ],
})
export class BlogModule {}

Dieses umfassende Beispiel zeigt, wie Module in NestJS als organisatorische Einheiten fungieren, die verwandte Funktionalitäten kapseln, klare Abhängigkeiten definieren und eine saubere Architektur fördern. Die modulare Struktur ermöglicht es, komplexe Anwendungen in überschaubare, testbare und wartbare Komponenten zu unterteilen.