Salve once again, legionary! Now we will learn the secrets of organizing the legion in our Empire. Every experienced consul knows that good organization and a clear division of responsibilities is the key to the success of every campaign!
In NestJS, modules are like different structures of the Empire - each has its own purpose, but they all work together, creating a smoothly functioning imperial machine!
1// legion.module.ts - legion structure
2import { Module } from '@nestjs/common';
3import { LegionController } from './legion.controller';
4import { LegionService } from './legion.service';
5import { LegionaryValidator } from './legionary.validator';
6
7@Module({
8 controllers: [LegionController], // Centurions of the structures
9 providers: [LegionService, LegionaryValidator], // Specialists and tools
10 exports: [LegionService], // What we make available to other structures
11})
12export class LegionModule {}Sometimes one structure of the Empire needs help from another:
1// tributes.module.ts - tributes structure
2@Module({
3 imports: [LegionModule], // We import the legion structure
4 controllers: [TributesController],
5 providers: [
6 TributesService,
7 TributesMapper,
8 TributesGuard,
9 ],
10 exports: [TributesService],
11})
12export class TributesModule {}
13
14// In the tributes service we can use the legion service
15@Injectable()
16export class TributesService {
17 constructor(
18 private readonly legionService: LegionService, // Available thanks to import + export
19 ) {}
20
21 async assignTributeCollection(provinceId: string) {
22 const availableLegionaries = await this.legionService.getAvailableLegionaries();
23 return this.legionService.assignMission(availableLegionaries[0], provinceId);
24 }
25}Some services are needed throughout the entire Empire - like the communication system:
1// communication.module.ts
2@Global() // Available everywhere without importing
3@Module({
4 providers: [
5 SignalService,
6 MessageService,
7 EmergencyService,
8 ],
9 exports: [SignalService, MessageService, EmergencyService],
10})
11export class CommunicationModule {}DI in NestJS is like the hierarchy and division of responsibilities in a legion - everyone knows who to take orders from and whom to pass information to.
1// Engineering service - route specialist
2@Injectable()
3export class EngineeringService {
4 private currentRoute: Route;
5
6 calculateRoute(from: Coordinates, to: Coordinates): Route {
7 // Complex route calculations (Via Appia)
8 return {
9 distance: this.calculateDistance(from, to),
10 estimatedTime: this.calculateTime(from, to),
11 waypoints: this.generateWaypoints(from, to),
12 };
13 }
14}
15
16// Forum controller - uses the engineer
17@Controller('forum')
18export class ForumController {
19 constructor(
20 private readonly engineeringService: EngineeringService, // Automatic injection
21 ) {}
22
23 @Post('march-to')
24 setMarch(@Body() destination: Coordinates) {
25 const route = this.engineeringService.calculateRoute(
26 this.getCurrentPosition(),
27 destination
28 );
29 return {
30 message: 'Campaign route set, consul!',
31 route,
32 };
33 }
34}Sometimes we need to be more precise in specifying what we need:
1// We define tokens for different types of configuration
2export const IMPERIUM_CONFIG = 'IMPERIUM_CONFIG';
3export const ARMORY_CONFIG = 'ARMORY_CONFIG';
4
5// In the module we define providers with tokens
6@Module({
7 providers: [
8 {
9 provide: IMPERIUM_CONFIG,
10 useValue: {
11 name: 'Roman Imperium',
12 maxMarchSpeed: 50,
13 legionCapacity: 5000,
14 ballistaCount: 32,
15 },
16 },
17 {
18 provide: ARMORY_CONFIG,
19 useValue: {
20 defaultPila: 100,
21 repairTime: 30,
22 range: 500,
23 },
24 },
25 ForumService,
26 ],
27})
28export class ForumModule {}
29
30// In the service we use tokens for injection
31@Injectable()
32export class ForumService {
33 constructor(
34 @Inject(IMPERIUM_CONFIG) private imperiumConfig: any,
35 @Inject(ARMORY_CONFIG) private armoryConfig: any,
36 ) {}
37
38 getImperiumInfo() {
39 return {
40 ...this.imperiumConfig,
41 armoryStatus: this.armoryConfig,
42 };
43 }
44}Sometimes we need to create complex objects:
1// Factory for creating different types of weapons
2const armoryFactory = {
3 provide: 'ARMORY_FACTORY',
4 useFactory: (config: any, logger: Logger) => {
5 return (type: string) => {
6 switch (type) {
7 case 'ballista':
8 return new Ballista(config.ballistaConfig, logger);
9 case 'pilum':
10 return new Pilum(config.pilumConfig, logger);
11 case 'gladius':
12 return new Gladius(config.gladiusConfig);
13 default:
14 throw new Error(\`Unknown weapon type: \${type}\`);
15 }
16 };
17 },
18 inject: [ARMORY_CONFIG, Logger],
19};
20
21@Module({
22 providers: [armoryFactory],
23 exports: ['ARMORY_FACTORY'],
24})
25export class ArmoryModule {}Some resources must be loaded asynchronously:
1// Async provider for connecting to the province map
2const provinceMapProvider = {
3 provide: 'PROVINCE_MAP_CONNECTION',
4 useFactory: async (): Promise<ProvinceMapConnection> => {
5 const connection = new ProvinceMapConnection();
6 await connection.connect('https://roman-province-maps.com');
7 await connection.authenticate(process.env.IMPERIUM_API_KEY);
8 return connection;
9 },
10};
11
12@Module({
13 providers: [provinceMapProvider],
14 exports: ['PROVINCE_MAP_CONNECTION'],
15})
16export class ProvinceMapModule {}1// Logging service for each request separately
2@Injectable({ scope: Scope.REQUEST })
3export class RequestLoggerService {
4 private requestId: string;
5
6 constructor(@Inject(REQUEST) private request: Request) {
7 this.requestId = Math.random().toString(36).substr(2, 9);
8 }
9
10 log(message: string) {
11 console.log(\`[\${this.requestId}] \${message}\`);
12 }
13}1// Each injection creates a new instance
2@Injectable({ scope: Scope.TRANSIENT })
3export class CampaignCalculatorService {
4 calculateDamage(attackPower: number, defense: number): number {
5 // Each campaign means new calculations
6 const randomFactor = Math.random() * 0.2 + 0.9; // 90-110% base damage
7 return Math.max(1, Math.floor((attackPower - defense) * randomFactor));
8 }
9}Sometimes we need to create modules with different configurations:
1// Dynamic database module
2@Module({})
3export class DatabaseModule {
4 static forRoot(config: DatabaseConfig): DynamicModule {
5 return {
6 module: DatabaseModule,
7 providers: [
8 {
9 provide: 'DATABASE_CONNECTION',
10 useFactory: () => createConnection(config),
11 },
12 DatabaseService,
13 ],
14 exports: ['DATABASE_CONNECTION', DatabaseService],
15 global: true,
16 };
17 }
18
19 static forFeature(entities: any[]): DynamicModule {
20 return {
21 module: DatabaseModule,
22 providers: entities.map(entity => ({
23 provide: \`\${entity.name}_REPOSITORY\`,
24 useFactory: (connection: any) => connection.getRepository(entity),
25 inject: ['DATABASE_CONNECTION'],
26 })),
27 exports: entities.map(entity => \`\${entity.name}_REPOSITORY\`),
28 };
29 }
30}
31
32// Usage in the main module
33@Module({
34 imports: [
35 DatabaseModule.forRoot({
36 host: 'localhost',
37 port: 5432,
38 database: 'roman_tributes',
39 }),
40 DatabaseModule.forFeature([Legionary, Tribute, Forum]),
41 ],
42})
43export class AppModule {}1// Main application module
2@Module({
3 imports: [
4 // Basic configuration
5 ConfigModule.forRoot({
6 isGlobal: true,
7 envFilePath: '.env',
8 }),
9
10 // Database
11 DatabaseModule.forRoot(databaseConfig),
12
13 // Functional modules
14 LegionModule,
15 TributesModule,
16 CampaignModule,
17 EngineeringModule,
18
19 // Shared modules
20 CommunicationModule,
21 LoggingModule,
22 ],
23 controllers: [AppController],
24 providers: [
25 AppService,
26 // Global guards, interceptors, filters
27 {
28 provide: APP_GUARD,
29 useClass: AuthGuard,
30 },
31 {
32 provide: APP_INTERCEPTOR,
33 useClass: LoggingInterceptor,
34 },
35 ],
36})
37export class AppModule implements NestModule {
38 configure(consumer: MiddlewareConsumer) {
39 consumer
40 .apply(ImperiumAuthMiddleware)
41 .forRoutes('*');
42 }
43}Good module organization is the foundation of a scalable NestJS application. Just as a well-organized legion can conquer any campaign, a well-designed module architecture will help you build an application that grows with your needs!
In the next module we will learn the details of controllers - our centurions who receive orders from the outside and manage operations!