We use cookies to enhance your experience on the site
CodeWorlds

NestJS Architecture - building the Roman Empire

Salve once again! Now that you know what NestJS is, it's time to learn the architecture of our Roman Empire. Every experienced consul knows that good construction of the Empire is the foundation of a successful campaign in the dangerous provinces of enterprise applications!

Architectural patterns in NestJS

NestJS is based on proven patterns known from other frameworks and programming languages. It's as if combining the best Empire-building traditions from different eras:

1. Model-View-Controller (MVC)

1// Model - the structure of our data (tributes)
2export class Tribute {
3 id: number;
4 name: string;
5 value: number;
6 province: string;
7 collected: boolean;
8}
9
10// Controller - consul managing operations
11@Controller('tributes')
12export class TributesController {
13 constructor(private tributesService: TributesService) {}
14
15 @Get()
16 findAll() {
17 return this.tributesService.findAll();
18 }
19
20 @Post()
21 create(@Body() tribute: CreateTributeDto) {
22 return this.tributesService.create(tribute);
23 }
24}
25
26// Service - experienced tribute collector
27@Injectable()
28export class TributesService {
29 private tributes: Tribute[] = [];
30
31 findAll(): Tribute[] {
32 return this.tributes;
33 }
34
35 create(tribute: CreateTributeDto): Tribute {
36 const newTribute = { ...tribute, id: Date.now() };
37 this.tributes.push(newTribute);
38 return newTribute;
39 }
40}

2. Dependency Injection - hierarchy in the Empire

Every legionary knows where to seek help and whom to notify about problems:

1// The Consul needs an engineer and a cook
2@Controller('forum')
3export class ForumController {
4 constructor(
5 private readonly engineeringService: EngineeringService,
6 private readonly cookService: CookService,
7 private readonly weatherService: WeatherService,
8 ) {}
9
10 @Get('status')
11 getForumStatus() {
12 return {
13 position: this.engineeringService.getCurrentPosition(),
14 meal: this.cookService.getTodaysMenu(),
15 weather: this.weatherService.getWeatherReport(),
16 };
17 }
18}

Core architectural elements

Modules - structures of the Empire

Each module is an independent structure of the Empire with its own purpose:

1// Legion module - manages legionaries
2@Module({
3 imports: [DatabaseModule],
4 controllers: [LegionController],
5 providers: [LegionService, LegionaryValidator],
6 exports: [LegionService], // Makes available for other modules
7})
8export class LegionModule {}
9
10// Main Empire module - connects all structures
11@Module({
12 imports: [
13 LegionModule,
14 TributesModule,
15 EngineeringModule,
16 ArmoryModule,
17 ],
18 controllers: [AppController],
19 providers: [AppService],
20})
21export class AppModule {}

Controllers - centurions of the structures

Controllers handle HTTP requests - they are the centurions of individual Empire structures:

1@Controller('armory')
2export class ArmoryController {
3 constructor(private armoryService: ArmoryService) {}
4
5 // Check armory status
6 @Get('status')
7 getArmoryStatus() {
8 return this.armoryService.getInventory();
9 }
10
11 // Add a new weapon
12 @Post()
13 addWeapon(@Body() weapon: CreateWeaponDto) {
14 return this.armoryService.addWeapon(weapon);
15 }
16
17 // Use weapon (attack!)
18 @Post(':id/attack')
19 attackWithWeapon(@Param('id') id: string) {
20 return this.armoryService.useWeapon(+id);
21 }
22
23 // Repair weapon
24 @Patch(':id/repair')
25 repairWeapon(@Param('id') id: string) {
26 return this.armoryService.repairWeapon(+id);
27 }
28}

Services - specialists in the Empire

Services contain the business logic - they are the experienced Roman specialists:

1@Injectable()
2export class EngineeringService {
3 private currentPosition = { lat: 0, lng: 0 };
4 private destination = null;
5
6 getCurrentPosition() {
7 return this.currentPosition;
8 }
9
10 setDestination(coordinates: { lat: number; lng: number }) {
11 this.destination = coordinates;
12 return this.calculateRoute();
13 }
14
15 private calculateRoute() {
16 // Complex engineering logic (Via Appia)
17 const distance = this.calculateDistance(this.currentPosition, this.destination);
18 const estimatedTime = distance / 50; // 50 km per day of marching
19
20 return {
21 distance,
22 estimatedTime,
23 route: this.generateWaypoints(),
24 };
25 }
26
27 calculateDistance(from: any, to: any): number {
28 // Imperial distance formula
29 return Math.sqrt(
30 Math.pow(to.lat - from.lat, 2) + Math.pow(to.lng - from.lng, 2)
31 ) * 111; // conversion to kilometers
32 }
33}

Advanced patterns

Guards - Praetorian guards

Guards control access - like Praetorian guards checking whether citizens have the right to enter certain structures of the Empire:

1@Injectable()
2export class ConsulGuard implements CanActivate {
3 canActivate(context: ExecutionContext): boolean {
4 const request = context.switchToHttp().getRequest();
5 const user = request.user;
6
7 // Only the consul has access to certain structures
8 return user && user.rank === 'consul';
9 }
10}
11
12// Using the guard
13@Controller('treasury')
14@UseGuards(ConsulGuard)
15export class TreasuryController {
16 @Get('gold')
17 getImperialTreasury() {
18 return 'Top secret imperial gold: 10000 aureus';
19 }
20}

Interceptors - monitoring systems

Interceptors intercept and modify requests/responses - like monitoring systems in the Empire:

1@Injectable()
2export class LoggingInterceptor implements NestInterceptor {
3 intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
4 const request = context.switchToHttp().getRequest();
5 const method = request.method;
6 const url = request.url;
7
8 console.log(\`⚔️ Imperial action: \${method} \${url}\`);
9
10 const start = Date.now();
11 return next.handle().pipe(
12 tap(() => {
13 const duration = Date.now() - start;
14 console.log(\`✅ Action completed in \${duration}ms\`);
15 }),
16 );
17 }
18}

Middleware - defense systems

Middleware operates before controllers - like defense systems checking everyone who enters the Empire:

1@Injectable()
2export class ImperiumMiddleware implements NestMiddleware {
3 use(req: Request, res: Response, next: NextFunction) {
4 // Check if the request comes from an Empire citizen
5 const userAgent = req.headers['user-agent'];
6
7 if (!userAgent?.includes('RomanImperium')) {
8 console.log('⚠️ Suspicious activity detected!');
9 }
10
11 // Add timestamp to each request
12 req['timestamp'] = new Date().toISOString();
13 next();
14 }
15}

DTOs and Validation - registry control

DTOs (Data Transfer Objects) define data structure - like imperial registries:

1import { IsString, IsNumber, IsOptional, Min, Max } from 'class-validator';
2
3export class CreateLegionaryDto {
4 @IsString()
5 name: string;
6
7 @IsString()
8 specialization: string;
9
10 @IsNumber()
11 @Min(1)
12 @Max(10)
13 experienceLevel: number;
14
15 @IsOptional()
16 @IsString()
17 backstory?: string;
18}
19
20// Usage in the controller
21@Post('legion')
22addLegionary(@Body() newLegionary: CreateLegionaryDto) {
23 return this.legionService.recruitLegionary(newLegionary);
24}

Full architecture example

1// imperium.module.ts - main module
2@Module({
3 imports: [
4 TypeOrmModule.forRoot(databaseConfig),
5 LegionModule,
6 TributesModule,
7 CampaignModule,
8 ],
9 controllers: [AppController],
10 providers: [
11 AppService,
12 {
13 provide: APP_GUARD,
14 useClass: AuthGuard,
15 },
16 {
17 provide: APP_INTERCEPTOR,
18 useClass: LoggingInterceptor,
19 },
20 ],
21})
22export class ImperiumModule {
23 configure(consumer: MiddlewareConsumer) {
24 consumer
25 .apply(ImperiumMiddleware)
26 .forRoutes('*');
27 }
28}

Architecture best practices

  1. Single responsibility - each service has one task
  2. Loose coupling - modules are independent
  3. Dependency Injection - everything through the constructor
  4. Input validation - DTOs with decorators
  5. Layer separation - controllers, services, repositories
  6. Error handling - exception filters
  7. Documentation - Swagger for API

NestJS architecture is like a well-designed Roman Empire - each structure has its place and purpose, everything works together harmoniously, and the whole system is ready for the greatest campaigns in the provinces of code!

In the next module we will learn the details of controller implementation - our loyal centurions!

Go to CodeWorlds