@bluealba/platform-cli 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/dist/index.js +278 -15
  2. package/docs/404.mdx +5 -0
  3. package/docs/architecture/api-explorer.mdx +478 -0
  4. package/docs/architecture/architecture-diagrams.mdx +12 -0
  5. package/docs/architecture/authentication-system.mdx +903 -0
  6. package/docs/architecture/authorization-system.mdx +886 -0
  7. package/docs/architecture/bootstrap.mdx +1442 -0
  8. package/docs/architecture/gateway-architecture.mdx +845 -0
  9. package/docs/architecture/multi-tenancy.mdx +1150 -0
  10. package/docs/architecture/overview.mdx +776 -0
  11. package/docs/architecture/scheduler.mdx +818 -0
  12. package/docs/architecture/shell.mdx +885 -0
  13. package/docs/architecture/ui-extension-points.mdx +781 -0
  14. package/docs/architecture/user-states.mdx +794 -0
  15. package/docs/development/overview.mdx +21 -0
  16. package/docs/development/workflow.mdx +914 -0
  17. package/docs/getting-started/core-concepts.mdx +892 -0
  18. package/docs/getting-started/installation.mdx +780 -0
  19. package/docs/getting-started/overview.mdx +83 -0
  20. package/docs/getting-started/quick-start.mdx +940 -0
  21. package/docs/guides/adding-documentation-sites.mdx +1367 -0
  22. package/docs/guides/creating-services.mdx +1736 -0
  23. package/docs/guides/creating-ui-modules.mdx +1860 -0
  24. package/docs/guides/identity-providers.mdx +1007 -0
  25. package/docs/guides/mermaid-diagrams.mdx +212 -0
  26. package/docs/guides/using-feature-flags.mdx +1059 -0
  27. package/docs/guides/working-with-rooms.mdx +566 -0
  28. package/docs/index.mdx +57 -0
  29. package/docs/platform-cli/commands.mdx +604 -0
  30. package/docs/platform-cli/overview.mdx +195 -0
  31. package/package.json +5 -2
  32. package/skills/ba-platform/platform-cli.skill.md +26 -0
  33. package/skills/ba-platform/platform.skill.md +35 -0
  34. package/templates/application-monorepo-template/gitignore +95 -0
  35. package/templates/bootstrap-service-template/Dockerfile.development +1 -1
  36. package/templates/bootstrap-service-template/gitignore +57 -0
  37. package/templates/bootstrap-service-template/package.json +1 -1
  38. package/templates/bootstrap-service-template/src/main.ts +6 -16
  39. package/templates/customization-ui-module-template/Dockerfile.development +1 -1
  40. package/templates/customization-ui-module-template/gitignore +73 -0
  41. package/templates/nestjs-service-module-template/Dockerfile.development +1 -1
  42. package/templates/nestjs-service-module-template/gitignore +56 -0
  43. package/templates/platform-init-template/{{platformName}}-core/gitignore +97 -0
  44. package/templates/platform-init-template/{{platformName}}-core/local/.env.example +1 -1
  45. package/templates/platform-init-template/{{platformName}}-core/local/platform-docker-compose.yml +1 -1
  46. package/templates/platform-init-template/{{platformName}}-core/local/{{platformName}}-core-docker-compose.yml +0 -1
  47. package/templates/react-ui-module-template/Dockerfile +1 -1
  48. package/templates/react-ui-module-template/Dockerfile.development +1 -3
  49. package/templates/react-ui-module-template/caddy/Caddyfile +1 -1
  50. package/templates/react-ui-module-template/gitignore +72 -0
  51. package/templates/react-ui-module-template/Dockerfile_nginx +0 -11
  52. package/templates/react-ui-module-template/nginx/default.conf +0 -23
@@ -0,0 +1,1736 @@
1
+ ---
2
+ title: Creating Backend Services
3
+ description: Complete guide to creating NestJS-based backend services for the Blue Alba Platform
4
+ ---
5
+
6
+ import { Card, CardGrid, Aside, Tabs, TabItem } from '@astrojs/starlight/components';
7
+ import MermaidDiagram from '~/components/MermaidDiagram.astro';
8
+
9
+ The Blue Alba Platform uses a microservices architecture with NestJS + Fastify as the primary framework for building high-performance, scalable backend services. This guide walks you through everything you need to create production-ready services that integrate seamlessly with the platform.
10
+
11
+ ## Overview
12
+
13
+ Backend services in the Blue Alba Platform are independent, self-contained applications that handle specific business domains. They communicate with the gateway, integrate with databases, publish events, and provide RESTful APIs for frontend applications.
14
+
15
+ ---
16
+
17
+ ## Architecture
18
+
19
+ The platform's microservices architecture uses a gateway pattern to route requests, handle authentication, and aggregate API documentation.
20
+
21
+ <MermaidDiagram
22
+ title="Microservices Architecture"
23
+ code={`graph TB
24
+ A[Client Applications<br/>React UI Modules]:::client --> B[API Gateway<br/>pae-nestjs-gateway-service]:::gateway
25
+
26
+ B --> C[Application Catalog]:::catalog
27
+ B --> D[Authentication]:::auth
28
+ B --> E[Authorization]:::authz
29
+
30
+ B --> F[Microservice 1<br/>Habits Service]:::service
31
+ B --> G[Microservice 2<br/>Custom Service]:::service
32
+ B --> H[Microservice N<br/>...]:::service
33
+
34
+ F --> I[(PostgreSQL)]:::database
35
+ G --> I
36
+ H --> I
37
+
38
+ F --> J[Kafka<br/>Event Bus]:::kafka
39
+ G --> J
40
+ H --> J
41
+
42
+ K[pae-service-nestjs-sdk<br/>Service SDK]:::sdk --> F
43
+ K --> G
44
+ K --> H
45
+
46
+ L[pae-core<br/>Domain Entities]:::core --> B
47
+ L --> F
48
+ L --> G
49
+ L --> H
50
+
51
+ classDef client fill:#90EE90,color:#333
52
+ classDef gateway fill:#FFD700,color:#333
53
+ classDef catalog fill:#87CEEB,color:#333
54
+ classDef auth fill:#DDA0DD,color:#333
55
+ classDef authz fill:#FFB6C1,color:#333
56
+ classDef service fill:#87CEEB,color:#333
57
+ classDef database fill:#F0E68C,color:#333
58
+ classDef kafka fill:#FFA07A,color:#333
59
+ classDef sdk fill:#ADD8E6,color:#333
60
+ classDef core fill:#FFB6C1,color:#333`}
61
+ />
62
+
63
+ ### Architecture Components
64
+
65
+ - **API Gateway**: Routes requests, handles authentication/authorization, aggregates API docs
66
+ - **Microservices**: Domain-specific services (NestJS or Express) handling business logic
67
+ - **Application Catalog**: Registry of all services and their routes
68
+ - **Database**: PostgreSQL for persistence (shared or per-service)
69
+ - **PAE Service SDK**: NestJS SDK providing common utilities and platform integration
70
+ - **PAE Core**: Shared domain entities, types, and authorization logic
71
+
72
+ ---
73
+
74
+ ## Quick Start
75
+
76
+ Create your first NestJS service in these steps:
77
+
78
+ ### 1. Install NestJS CLI
79
+
80
+ ```bash
81
+ npm install -g @nestjs/cli
82
+ ```
83
+
84
+ ### 2. Create New Service
85
+
86
+ ```bash
87
+ # Create new NestJS project
88
+ nest new my-service
89
+
90
+ # Navigate to project
91
+ cd my-service
92
+ ```
93
+
94
+ When prompted, choose **npm** as the package manager.
95
+
96
+ ### 3. Install Dependencies
97
+
98
+ ```bash
99
+ # Install platform dependencies
100
+ npm install @bluealba/pae-service-nestjs-sdk
101
+ npm install @bluealba/pae-core
102
+
103
+ # Install NestJS core dependencies
104
+ npm install @nestjs/common@^11.0.0
105
+ npm install @nestjs/core@^11.0.0
106
+ npm install @nestjs/config@^4.0.2
107
+ npm install @nestjs/platform-fastify@^11.0.0
108
+
109
+ # Install Fastify
110
+ npm install fastify@5.4.0
111
+
112
+ # Install validation dependencies
113
+ npm install class-validator class-transformer
114
+
115
+ # Install HTTP client
116
+ npm install @nestjs/axios
117
+ ```
118
+
119
+ ### 4. Update TypeScript Configuration
120
+
121
+ Edit `tsconfig.json`:
122
+
123
+ ```json
124
+ {
125
+ "compilerOptions": {
126
+ "module": "commonjs",
127
+ "declaration": true,
128
+ "removeComments": true,
129
+ "emitDecoratorMetadata": true,
130
+ "experimentalDecorators": true,
131
+ "allowSyntheticDefaultImports": true,
132
+ "target": "ES2021",
133
+ "sourceMap": true,
134
+ "outDir": "./dist",
135
+ "baseUrl": "./",
136
+ "incremental": true,
137
+ "skipLibCheck": true,
138
+ "strictNullChecks": false,
139
+ "noImplicitAny": false,
140
+ "strictBindCallApply": false,
141
+ "forceConsistentCasingInFileNames": false,
142
+ "noFallthroughCasesInSwitch": false
143
+ }
144
+ }
145
+ ```
146
+
147
+ ### 5. Configure Entry Point (main.ts)
148
+
149
+ Replace the contents of `src/main.ts`:
150
+
151
+ ```typescript
152
+ import { ValidationPipe } from '@nestjs/common';
153
+ import { NestFactory } from '@nestjs/core';
154
+ import { FastifyAdapter, NestFastifyApplication } from '@nestjs/platform-fastify';
155
+ import { AppModule } from './app.module';
156
+
157
+ async function bootstrap() {
158
+ const app = await NestFactory.create<NestFastifyApplication>(
159
+ AppModule,
160
+ new FastifyAdapter({
161
+ trustProxy: true,
162
+ }),
163
+ );
164
+
165
+ // Enable validation pipes for DTOs
166
+ app.useGlobalPipes(new ValidationPipe());
167
+
168
+ await app.listen({
169
+ host: '0.0.0.0',
170
+ port: 80,
171
+ });
172
+ }
173
+
174
+ bootstrap();
175
+ ```
176
+
177
+ ### 6. Configure App Module
178
+
179
+ Update `src/app.module.ts`:
180
+
181
+ ```typescript
182
+ import { PAEServiceModule } from '@bluealba/pae-service-nestjs-sdk';
183
+ import { HttpModule } from '@nestjs/axios';
184
+ import { Module } from '@nestjs/common';
185
+ import { ConfigModule } from '@nestjs/config';
186
+ import { AppController } from './app.controller';
187
+ import { AppService } from './app.service';
188
+
189
+ @Module({
190
+ imports: [
191
+ // Global configuration
192
+ ConfigModule.forRoot({
193
+ isGlobal: true,
194
+ }),
195
+
196
+ // HTTP client for external requests
197
+ HttpModule.register({
198
+ global: true,
199
+ }),
200
+
201
+ // Platform integration (health, version, changelog endpoints)
202
+ PAEServiceModule.forRoot({
203
+ healthPath: '/_/health',
204
+ versionPath: '/_/version',
205
+ changelogPath: '/_/changelog',
206
+ }),
207
+ ],
208
+ controllers: [AppController],
209
+ providers: [AppService],
210
+ })
211
+ export class AppModule {}
212
+ ```
213
+
214
+ ### 7. Update Package Scripts
215
+
216
+ Update `package.json` scripts:
217
+
218
+ ```json
219
+ {
220
+ "scripts": {
221
+ "build": "nest build",
222
+ "start:dev": "nest start --watch",
223
+ "start:prod": "node dist/src/main",
224
+ "test": "jest",
225
+ "test:unit": "jest",
226
+ "test:e2e": "jest --config ./test/jest-e2e.json",
227
+ "test:watch": "jest --watch"
228
+ }
229
+ }
230
+ ```
231
+
232
+ ### 8. Test Your Service
233
+
234
+ ```bash
235
+ # Run in development mode
236
+ npm run start:dev
237
+
238
+ # Service should be available at http://localhost:80
239
+ # Health check: http://localhost:80/_/health
240
+ # Version info: http://localhost:80/_/version
241
+ ```
242
+
243
+ ---
244
+
245
+ ## Project Structure
246
+
247
+ A typical NestJS service follows this structure:
248
+
249
+ ```
250
+ my-service/
251
+ ├── package.json # Project configuration
252
+ ├── tsconfig.json # TypeScript configuration
253
+ ├── nest-cli.json # NestJS CLI configuration
254
+ ├── jest.config.js # Jest testing configuration
255
+ ├── Dockerfile.development # Development Docker image
256
+ ├── Dockerfile # Production Docker image
257
+ └── src/
258
+ ├── main.ts # Application entry point
259
+ ├── app.module.ts # Root application module
260
+ ├── app.controller.ts # Example controller
261
+ ├── app.service.ts # Example service
262
+ ├── config/ # Configuration files
263
+ │ └── configuration.ts
264
+ ├── users/ # Feature module (example)
265
+ │ ├── users.module.ts
266
+ │ ├── users.controller.ts
267
+ │ ├── users.service.ts
268
+ │ ├── dto/
269
+ │ │ ├── create-user.dto.ts
270
+ │ │ └── update-user.dto.ts
271
+ │ └── entities/
272
+ │ └── user.entity.ts
273
+ ├── common/ # Shared utilities
274
+ │ ├── guards/
275
+ │ ├── interceptors/
276
+ │ ├── filters/
277
+ │ └── decorators/
278
+ └── database/ # Database integration
279
+ └── database.module.ts
280
+ ```
281
+
282
+ ### Key Directories Explained
283
+
284
+ <CardGrid>
285
+ <Card title="src/" icon="folder">
286
+ Main source code directory containing all application logic.
287
+ </Card>
288
+
289
+ <Card title="src/[feature]/" icon="puzzle">
290
+ Feature modules organized by domain (users, products, orders, etc.).
291
+ </Card>
292
+
293
+ <Card title="dto/" icon="document">
294
+ Data Transfer Objects for request validation and API contracts.
295
+ </Card>
296
+
297
+ <Card title="entities/" icon="seti:db">
298
+ Domain entities representing data models and business logic.
299
+ </Card>
300
+
301
+ <Card title="common/" icon="setting">
302
+ Shared utilities like guards, interceptors, decorators, and filters.
303
+ </Card>
304
+
305
+ <Card title="test/" icon="approve-check">
306
+ E2E tests for integration testing across modules.
307
+ </Card>
308
+ </CardGrid>
309
+
310
+ ---
311
+
312
+ ## Development Workflow
313
+
314
+ ### Running Locally with Docker
315
+
316
+ The recommended way to develop services is using Docker Compose, which provides a consistent environment and automatic integration with the platform.
317
+
318
+ ### Project Structure Setup
319
+
320
+ Your repositories should be organized at the same level:
321
+
322
+ ```
323
+ my-projects/
324
+ ├── my-product-local/ # Local environment with docker-compose.yml
325
+ │ └── docker-compose.yml
326
+ └── my-service/ # Your service repository
327
+ ├── Dockerfile.development
328
+ ├── package.json
329
+ └── src/
330
+ ```
331
+
332
+ <Aside type="note">
333
+ The docker-compose.yml uses `${PWD}/../my-service` to reference the service repository. This requires both repositories to be siblings in the same parent directory.
334
+ </Aside>
335
+
336
+ ### Step 1: Create Development Dockerfile
337
+
338
+ Create `Dockerfile.development` in your service repository:
339
+
340
+ ```dockerfile
341
+ FROM node:20-alpine as development
342
+
343
+ ENV NODE_ENV=development
344
+ ENV PORT=80
345
+
346
+ ARG BA_NPM_AUTH_TOKEN
347
+
348
+ WORKDIR /app
349
+
350
+ COPY package*.json ./
351
+
352
+ RUN echo "@bluealba:registry=https://bluealba.jfrog.io/artifactory/api/npm/npm-release-virtual/" > .npmrc
353
+ RUN echo "//bluealba.jfrog.io/artifactory/api/npm/npm-release-virtual/:_auth=${BA_NPM_AUTH_TOKEN}" >> .npmrc
354
+
355
+ RUN npm install
356
+
357
+ EXPOSE 80
358
+
359
+ CMD ["npm", "run", "start:dev"]
360
+ ```
361
+
362
+ **Key points:**
363
+ - Uses Node 20 Alpine for a lightweight image
364
+ - Sets `NODE_ENV=development` for development mode
365
+ - Exposes port 80 (configurable)
366
+ - Runs `start:dev` command for hot reload with NestJS watch mode
367
+
368
+ ### Step 2: Configure Docker Compose
369
+
370
+ Add your service to `docker-compose.yml` in your local environment repository:
371
+
372
+ ```yaml
373
+ services:
374
+ my-service:
375
+ build:
376
+ context: ../my-service
377
+ dockerfile: Dockerfile.development
378
+ args:
379
+ - BA_NPM_AUTH_TOKEN=$BA_NPM_AUTH_TOKEN
380
+ ports:
381
+ - 9002:80
382
+ environment:
383
+ - GATEWAY_URL=${PAE_GATEWAY_URL}
384
+ volumes:
385
+ - ${PWD}/../pae-sandbox-home-service:/app
386
+ ```
387
+
388
+ **Configuration explained:**
389
+
390
+ | Property | Description |
391
+ |----------|-------------|
392
+ | `context: ../my-service` | Path to your service repository (relative to compose file) |
393
+ | `dockerfile: Dockerfile.development` | Development-specific Dockerfile |
394
+ | `ports: 9002:80` | Maps host port 9002 to container port 80 |
395
+ | `volumes` | Mounts your code for hot reload (changes reflect immediately) |
396
+ | `environment` | Sets environment variables (database URL, etc.) |
397
+
398
+ ### Step 3: Start Development Environment
399
+
400
+ From your local environment repository:
401
+
402
+ ```bash
403
+ # Start your service
404
+ docker-compose up my-service
405
+
406
+ # Or start in detached mode
407
+ docker-compose up -d my-service
408
+
409
+ # View logs
410
+ docker-compose logs -f my-service
411
+
412
+ # Rebuild after dependency changes
413
+ docker-compose up --build my-service
414
+ ```
415
+
416
+ **What happens:**
417
+ 1. Docker builds the development image
418
+ 2. Installs npm dependencies inside the container
419
+ 3. Starts the NestJS dev server with watch mode (`nest start --watch`)
420
+ 4. Your code is mounted as a volume - changes trigger automatic rebuilds
421
+ 5. Service is accessible at `http://localhost:9002`
422
+
423
+ ### Step 4: Verify Service is Running
424
+
425
+ ```bash
426
+ # Check health endpoint
427
+ curl http://localhost:9002/_/health
428
+
429
+ # Check version endpoint
430
+ curl http://localhost:9002/_/version
431
+
432
+ # Check changelog endpoint
433
+ curl http://localhost:9002/_/changelog
434
+ ```
435
+
436
+ ---
437
+
438
+ ## Core Concepts
439
+
440
+ ### Controllers
441
+
442
+ Controllers handle incoming HTTP requests and return responses to the client. They use decorators to define routes and HTTP methods.
443
+
444
+ <Tabs>
445
+ <TabItem label="Basic Controller">
446
+
447
+ ```typescript
448
+ import { Controller, Get, Post, Body, Param, Patch, Delete } from '@nestjs/common';
449
+ import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
450
+ import { UsersService } from './users.service';
451
+ import { CreateUserDto } from './dto/create-user.dto';
452
+ import { UpdateUserDto } from './dto/update-user.dto';
453
+
454
+ @Controller('users')
455
+ @ApiTags('users')
456
+ export class UsersController {
457
+ constructor(private readonly usersService: UsersService) {}
458
+
459
+ @Post()
460
+ @ApiOperation({ summary: 'Create a new user' })
461
+ @ApiResponse({ status: 201, description: 'User created successfully' })
462
+ create(@Body() createUserDto: CreateUserDto) {
463
+ return this.usersService.create(createUserDto);
464
+ }
465
+
466
+ @Get()
467
+ @ApiOperation({ summary: 'Get all users' })
468
+ findAll() {
469
+ return this.usersService.findAll();
470
+ }
471
+
472
+ @Get(':id')
473
+ @ApiOperation({ summary: 'Get user by ID' })
474
+ findOne(@Param('id') id: string) {
475
+ return this.usersService.findOne(+id);
476
+ }
477
+
478
+ @Patch(':id')
479
+ @ApiOperation({ summary: 'Update user by ID' })
480
+ update(@Param('id') id: string, @Body() updateUserDto: UpdateUserDto) {
481
+ return this.usersService.update(+id, updateUserDto);
482
+ }
483
+
484
+ @Delete(':id')
485
+ @ApiOperation({ summary: 'Delete user by ID' })
486
+ remove(@Param('id') id: string) {
487
+ return this.usersService.remove(+id);
488
+ }
489
+ }
490
+ ```
491
+
492
+ </TabItem>
493
+
494
+ <TabItem label="Query Parameters">
495
+
496
+ ```typescript
497
+ import { Controller, Get, Query } from '@nestjs/common';
498
+ import { ApiQuery } from '@nestjs/swagger';
499
+
500
+ @Controller('users')
501
+ export class UsersController {
502
+ constructor(private readonly usersService: UsersService) {}
503
+
504
+ @Get()
505
+ @ApiQuery({ name: 'page', required: false, type: Number })
506
+ @ApiQuery({ name: 'limit', required: false, type: Number })
507
+ @ApiQuery({ name: 'search', required: false, type: String })
508
+ findAll(
509
+ @Query('page') page?: string,
510
+ @Query('limit') limit?: string,
511
+ @Query('search') search?: string,
512
+ ) {
513
+ return this.usersService.findAll({
514
+ page: page ? parseInt(page) : 1,
515
+ limit: limit ? parseInt(limit) : 10,
516
+ search,
517
+ });
518
+ }
519
+ }
520
+ ```
521
+
522
+ </TabItem>
523
+
524
+ <TabItem label="Request Headers">
525
+
526
+ ```typescript
527
+ import { Controller, Get, Headers, HttpCode } from '@nestjs/common';
528
+
529
+ @Controller('users')
530
+ export class UsersController {
531
+ constructor(private readonly usersService: UsersService) {}
532
+
533
+ @Get('profile')
534
+ @HttpCode(200)
535
+ getProfile(@Headers('authorization') authHeader: string) {
536
+ // Extract and validate token
537
+ const token = authHeader?.replace('Bearer ', '');
538
+ return this.usersService.getProfile(token);
539
+ }
540
+ }
541
+ ```
542
+
543
+ </TabItem>
544
+
545
+ <TabItem label="Async Operations">
546
+
547
+ ```typescript
548
+ import { Controller, Get, Post, Body } from '@nestjs/common';
549
+
550
+ @Controller('users')
551
+ export class UsersController {
552
+ constructor(private readonly usersService: UsersService) {}
553
+
554
+ @Post()
555
+ async create(@Body() createUserDto: CreateUserDto) {
556
+ // Async/await for database operations
557
+ const user = await this.usersService.create(createUserDto);
558
+
559
+ // Trigger async tasks (email, notifications, etc.)
560
+ this.usersService.sendWelcomeEmail(user.email);
561
+
562
+ return user;
563
+ }
564
+
565
+ @Get()
566
+ async findAll(): Promise<User[]> {
567
+ return await this.usersService.findAll();
568
+ }
569
+ }
570
+ ```
571
+
572
+ </TabItem>
573
+ </Tabs>
574
+
575
+ ### Services
576
+
577
+ Services contain business logic and are injected into controllers using dependency injection. They should be stateless and focused on a single responsibility.
578
+
579
+ <Tabs>
580
+ <TabItem label="Basic Service">
581
+
582
+ ```typescript
583
+ import { Injectable, NotFoundException } from '@nestjs/common';
584
+ import { CreateUserDto } from './dto/create-user.dto';
585
+ import { UpdateUserDto } from './dto/update-user.dto';
586
+ import { User } from './entities/user.entity';
587
+
588
+ @Injectable()
589
+ export class UsersService {
590
+ private users: User[] = [];
591
+ private idCounter = 1;
592
+
593
+ create(createUserDto: CreateUserDto): User {
594
+ const user: User = {
595
+ id: this.idCounter++,
596
+ ...createUserDto,
597
+ createdAt: new Date(),
598
+ };
599
+ this.users.push(user);
600
+ return user;
601
+ }
602
+
603
+ findAll(): User[] {
604
+ return this.users;
605
+ }
606
+
607
+ findOne(id: number): User {
608
+ const user = this.users.find(u => u.id === id);
609
+ if (!user) {
610
+ throw new NotFoundException(`User with ID ${id} not found`);
611
+ }
612
+ return user;
613
+ }
614
+
615
+ update(id: number, updateUserDto: UpdateUserDto): User {
616
+ const user = this.findOne(id);
617
+ Object.assign(user, updateUserDto);
618
+ return user;
619
+ }
620
+
621
+ remove(id: number): void {
622
+ const index = this.users.findIndex(u => u.id === id);
623
+ if (index === -1) {
624
+ throw new NotFoundException(`User with ID ${id} not found`);
625
+ }
626
+ this.users.splice(index, 1);
627
+ }
628
+ }
629
+ ```
630
+
631
+ </TabItem>
632
+
633
+ <TabItem label="Database Service">
634
+
635
+ ```typescript
636
+ import { Injectable, NotFoundException } from '@nestjs/common';
637
+ import { DatabaseService } from '../database/database.service';
638
+ import { CreateUserDto } from './dto/create-user.dto';
639
+ import { UpdateUserDto } from './dto/update-user.dto';
640
+ import { User } from './entities/user.entity';
641
+
642
+ @Injectable()
643
+ export class UsersService {
644
+ private readonly tableName = 'users';
645
+
646
+ constructor(private readonly dbService: DatabaseService) {}
647
+
648
+ async create(createUserDto: CreateUserDto): Promise<User> {
649
+ const [result] = await this.dbService.db(this.tableName)
650
+ .insert({
651
+ username: createUserDto.username,
652
+ email: createUserDto.email,
653
+ display_name: createUserDto.displayName,
654
+ created_at: new Date(),
655
+ })
656
+ .returning('*');
657
+
658
+ return User.fromDatabaseRow(result);
659
+ }
660
+
661
+ async findAll(): Promise<User[]> {
662
+ const result = await this.dbService.db(this.tableName)
663
+ .select('*')
664
+ .orderBy('created_at', 'desc');
665
+
666
+ return result.map(User.fromDatabaseRow);
667
+ }
668
+
669
+ async findOne(id: number): Promise<User> {
670
+ const [result] = await this.dbService.db(this.tableName)
671
+ .select('*')
672
+ .where('id', id);
673
+
674
+ if (!result) {
675
+ throw new NotFoundException(`User with ID ${id} not found`);
676
+ }
677
+
678
+ return User.fromDatabaseRow(result);
679
+ }
680
+
681
+ async update(id: number, updateUserDto: UpdateUserDto): Promise<User> {
682
+ const [result] = await this.dbService.db(this.tableName)
683
+ .update({
684
+ username: updateUserDto.username,
685
+ email: updateUserDto.email,
686
+ display_name: updateUserDto.displayName,
687
+ updated_at: new Date(),
688
+ })
689
+ .where('id', id)
690
+ .returning('*');
691
+
692
+ if (!result) {
693
+ throw new NotFoundException(`User with ID ${id} not found`);
694
+ }
695
+
696
+ return User.fromDatabaseRow(result);
697
+ }
698
+
699
+ async remove(id: number): Promise<void> {
700
+ const deleted = await this.dbService.db(this.tableName)
701
+ .delete()
702
+ .where('id', id);
703
+
704
+ if (!deleted) {
705
+ throw new NotFoundException(`User with ID ${id} not found`);
706
+ }
707
+ }
708
+ }
709
+ ```
710
+
711
+ </TabItem>
712
+
713
+ <TabItem label="Service with Caching">
714
+
715
+ ```typescript
716
+ import { Injectable, Inject, NotFoundException } from '@nestjs/common';
717
+ import { Cache } from 'cache-manager';
718
+ import { DatabaseService } from '../database/database.service';
719
+ import { User } from './entities/user.entity';
720
+
721
+ @Injectable()
722
+ export class UsersService {
723
+ constructor(
724
+ private readonly dbService: DatabaseService,
725
+ @Inject('USERS_CACHE') private readonly cache: Cache,
726
+ ) {}
727
+
728
+ async findAll(): Promise<User[]> {
729
+ // Cache for 5 minutes
730
+ return this.cache.wrap<User[]>(
731
+ 'all_users',
732
+ async () => {
733
+ const result = await this.dbService.db('users').select('*');
734
+ return result.map(User.fromDatabaseRow);
735
+ },
736
+ 300000
737
+ );
738
+ }
739
+
740
+ async findOne(id: number): Promise<User> {
741
+ return this.cache.wrap<User>(
742
+ `user_${id}`,
743
+ async () => {
744
+ const [result] = await this.dbService.db('users')
745
+ .select('*')
746
+ .where('id', id);
747
+
748
+ if (!result) {
749
+ throw new NotFoundException(`User with ID ${id} not found`);
750
+ }
751
+
752
+ return User.fromDatabaseRow(result);
753
+ },
754
+ 300000
755
+ );
756
+ }
757
+
758
+ async invalidateCache(id?: number): Promise<void> {
759
+ if (id) {
760
+ await this.cache.del(`user_${id}`);
761
+ }
762
+ await this.cache.del('all_users');
763
+ }
764
+ }
765
+ ```
766
+
767
+ </TabItem>
768
+
769
+ <TabItem label="Service with Dependencies">
770
+
771
+ ```typescript
772
+ import { Injectable } from '@nestjs/common';
773
+ import { HttpService } from '@nestjs/axios';
774
+ import { firstValueFrom } from 'rxjs';
775
+ import { EmailService } from '../email/email.service';
776
+ import { LoggerService } from '../logger/logger.service';
777
+ import { CreateUserDto } from './dto/create-user.dto';
778
+
779
+ @Injectable()
780
+ export class UsersService {
781
+ constructor(
782
+ private readonly httpService: HttpService,
783
+ private readonly emailService: EmailService,
784
+ private readonly logger: LoggerService,
785
+ ) {}
786
+
787
+ async create(createUserDto: CreateUserDto) {
788
+ this.logger.log('Creating new user', { username: createUserDto.username });
789
+
790
+ // Create user in database
791
+ const user = await this.saveUser(createUserDto);
792
+
793
+ // Send welcome email (async, don't block)
794
+ this.emailService.sendWelcomeEmail(user.email, user.displayName)
795
+ .catch(err => this.logger.error('Failed to send welcome email', err));
796
+
797
+ // Notify external system (await response)
798
+ try {
799
+ const response = await firstValueFrom(
800
+ this.httpService.post('https://external-api.com/users', user)
801
+ );
802
+ this.logger.log('User synced to external system', response.data);
803
+ } catch (error) {
804
+ this.logger.error('Failed to sync user to external system', error);
805
+ }
806
+
807
+ return user;
808
+ }
809
+
810
+ private async saveUser(createUserDto: CreateUserDto) {
811
+ // Implementation
812
+ }
813
+ }
814
+ ```
815
+
816
+ </TabItem>
817
+ </Tabs>
818
+
819
+ ### Modules
820
+
821
+ Modules organize your application into cohesive blocks of functionality. Each feature should be its own module.
822
+
823
+ <Tabs>
824
+ <TabItem label="Feature Module">
825
+
826
+ ```typescript
827
+ import { Module } from '@nestjs/common';
828
+ import { UsersController } from './users.controller';
829
+ import { UsersService } from './users.service';
830
+
831
+ @Module({
832
+ controllers: [UsersController],
833
+ providers: [UsersService],
834
+ exports: [UsersService], // Export if other modules need it
835
+ })
836
+ export class UsersModule {}
837
+ ```
838
+
839
+ </TabItem>
840
+
841
+ <TabItem label="Module with Dependencies">
842
+
843
+ ```typescript
844
+ import { Module } from '@nestjs/common';
845
+ import { DatabaseModule } from '../database/database.module';
846
+ import { EmailModule } from '../email/email.module';
847
+ import { UsersController } from './users.controller';
848
+ import { UsersService } from './users.service';
849
+
850
+ @Module({
851
+ imports: [
852
+ DatabaseModule, // Import other modules this module depends on
853
+ EmailModule,
854
+ ],
855
+ controllers: [UsersController],
856
+ providers: [UsersService],
857
+ exports: [UsersService],
858
+ })
859
+ export class UsersModule {}
860
+ ```
861
+
862
+ </TabItem>
863
+
864
+ <TabItem label="Global Module">
865
+
866
+ ```typescript
867
+ import { Global, Module } from '@nestjs/common';
868
+ import { LoggerService } from './logger.service';
869
+
870
+ @Global() // Make this module available everywhere
871
+ @Module({
872
+ providers: [LoggerService],
873
+ exports: [LoggerService],
874
+ })
875
+ export class LoggerModule {}
876
+ ```
877
+
878
+ </TabItem>
879
+
880
+ <TabItem label="Dynamic Module">
881
+
882
+ ```typescript
883
+ import { DynamicModule, Module } from '@nestjs/common';
884
+
885
+ interface DatabaseOptions {
886
+ host: string;
887
+ port: number;
888
+ database: string;
889
+ }
890
+
891
+ @Module({})
892
+ export class DatabaseModule {
893
+ static forRoot(options: DatabaseOptions): DynamicModule {
894
+ return {
895
+ module: DatabaseModule,
896
+ providers: [
897
+ {
898
+ provide: 'DATABASE_OPTIONS',
899
+ useValue: options,
900
+ },
901
+ DatabaseService,
902
+ ],
903
+ exports: [DatabaseService],
904
+ global: true,
905
+ };
906
+ }
907
+ }
908
+
909
+ // Usage in AppModule:
910
+ // DatabaseModule.forRoot({ host: 'localhost', port: 5432, database: 'mydb' })
911
+ ```
912
+
913
+ </TabItem>
914
+ </Tabs>
915
+
916
+ ### DTOs and Validation
917
+
918
+ Data Transfer Objects (DTOs) define the shape of data for requests and responses. Use `class-validator` for automatic validation.
919
+
920
+ <Tabs>
921
+ <TabItem label="Create DTO">
922
+
923
+ ```typescript
924
+ import { IsString, IsEmail, IsNotEmpty, MinLength, MaxLength } from 'class-validator';
925
+ import { ApiProperty } from '@nestjs/swagger';
926
+
927
+ export class CreateUserDto {
928
+ @ApiProperty({ description: 'User username', example: 'johndoe' })
929
+ @IsString()
930
+ @IsNotEmpty()
931
+ @MinLength(3)
932
+ @MaxLength(50)
933
+ username: string;
934
+
935
+ @ApiProperty({ description: 'User email address', example: 'john@example.com' })
936
+ @IsEmail()
937
+ @IsNotEmpty()
938
+ email: string;
939
+
940
+ @ApiProperty({ description: 'User display name', example: 'John Doe' })
941
+ @IsString()
942
+ @IsNotEmpty()
943
+ displayName: string;
944
+
945
+ @ApiProperty({ description: 'User password', example: 'StrongP@ss123' })
946
+ @IsString()
947
+ @IsNotEmpty()
948
+ @MinLength(8)
949
+ password: string;
950
+ }
951
+ ```
952
+
953
+ </TabItem>
954
+
955
+ <TabItem label="Update DTO">
956
+
957
+ ```typescript
958
+ import { PartialType } from '@nestjs/mapped-types';
959
+ import { CreateUserDto } from './create-user.dto';
960
+
961
+ // All fields optional for updates
962
+ export class UpdateUserDto extends PartialType(CreateUserDto) {}
963
+ ```
964
+
965
+ </TabItem>
966
+
967
+ <TabItem label="Advanced Validation">
968
+
969
+ ```typescript
970
+ import {
971
+ IsString,
972
+ IsNumber,
973
+ IsOptional,
974
+ IsEnum,
975
+ IsArray,
976
+ ValidateNested,
977
+ Min,
978
+ Max
979
+ } from 'class-validator';
980
+ import { Type } from 'class-transformer';
981
+ import { ApiProperty } from '@nestjs/swagger';
982
+
983
+ enum UserRole {
984
+ ADMIN = 'admin',
985
+ USER = 'user',
986
+ GUEST = 'guest',
987
+ }
988
+
989
+ class AddressDto {
990
+ @IsString()
991
+ street: string;
992
+
993
+ @IsString()
994
+ city: string;
995
+
996
+ @IsString()
997
+ zipCode: string;
998
+ }
999
+
1000
+ export class CreateUserDto {
1001
+ @ApiProperty()
1002
+ @IsString()
1003
+ username: string;
1004
+
1005
+ @ApiProperty({ enum: UserRole, default: UserRole.USER })
1006
+ @IsEnum(UserRole)
1007
+ @IsOptional()
1008
+ role?: UserRole = UserRole.USER;
1009
+
1010
+ @ApiProperty({ minimum: 18, maximum: 120 })
1011
+ @IsNumber()
1012
+ @Min(18)
1013
+ @Max(120)
1014
+ age: number;
1015
+
1016
+ @ApiProperty({ type: [String] })
1017
+ @IsArray()
1018
+ @IsString({ each: true })
1019
+ tags: string[];
1020
+
1021
+ @ApiProperty({ type: AddressDto })
1022
+ @ValidateNested()
1023
+ @Type(() => AddressDto)
1024
+ address: AddressDto;
1025
+ }
1026
+ ```
1027
+
1028
+ </TabItem>
1029
+
1030
+ <TabItem label="Custom Validators">
1031
+
1032
+ ```typescript
1033
+ import {
1034
+ registerDecorator,
1035
+ ValidationOptions,
1036
+ ValidatorConstraint,
1037
+ ValidatorConstraintInterface
1038
+ } from 'class-validator';
1039
+
1040
+ @ValidatorConstraint({ async: false })
1041
+ export class IsStrongPasswordConstraint implements ValidatorConstraintInterface {
1042
+ validate(password: string) {
1043
+ // At least 8 characters, 1 uppercase, 1 lowercase, 1 number, 1 special char
1044
+ const regex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;
1045
+ return regex.test(password);
1046
+ }
1047
+
1048
+ defaultMessage() {
1049
+ return 'Password must be at least 8 characters with uppercase, lowercase, number, and special character';
1050
+ }
1051
+ }
1052
+
1053
+ export function IsStrongPassword(validationOptions?: ValidationOptions) {
1054
+ return function (object: Object, propertyName: string) {
1055
+ registerDecorator({
1056
+ target: object.constructor,
1057
+ propertyName: propertyName,
1058
+ options: validationOptions,
1059
+ constraints: [],
1060
+ validator: IsStrongPasswordConstraint,
1061
+ });
1062
+ };
1063
+ }
1064
+
1065
+ // Usage
1066
+ export class CreateUserDto {
1067
+ @IsStrongPassword()
1068
+ password: string;
1069
+ }
1070
+ ```
1071
+
1072
+ </TabItem>
1073
+ </Tabs>
1074
+
1075
+ ### Dependency Injection
1076
+
1077
+ NestJS uses dependency injection to manage dependencies between classes. This promotes loose coupling and testability.
1078
+
1079
+ ```typescript
1080
+ // Logger Service
1081
+ @Injectable()
1082
+ export class LoggerService {
1083
+ log(message: string) {
1084
+ console.log(`[LOG]: ${message}`);
1085
+ }
1086
+ }
1087
+
1088
+ // Users Service with dependency
1089
+ @Injectable()
1090
+ export class UsersService {
1091
+ constructor(
1092
+ private readonly logger: LoggerService, // Injected automatically
1093
+ ) {}
1094
+
1095
+ create(user: CreateUserDto) {
1096
+ this.logger.log(`Creating user: ${user.username}`);
1097
+ // ...
1098
+ }
1099
+ }
1100
+
1101
+ // Module configuration
1102
+ @Module({
1103
+ providers: [
1104
+ LoggerService, // Register the service
1105
+ UsersService, // Can inject LoggerService
1106
+ ],
1107
+ exports: [UsersService],
1108
+ })
1109
+ export class UsersModule {}
1110
+ ```
1111
+
1112
+ ---
1113
+
1114
+ ## Platform Integration
1115
+
1116
+ ### Using PAE Service SDK
1117
+
1118
+ The PAE Service SDK provides common utilities for platform integration:
1119
+
1120
+ ```typescript
1121
+ import { PAEServiceModule } from '@bluealba/pae-service-nestjs-sdk';
1122
+ import { Module } from '@nestjs/common';
1123
+
1124
+ @Module({
1125
+ imports: [
1126
+ PAEServiceModule.forRoot({
1127
+ healthPath: '/_/health', // GET /_/health
1128
+ versionPath: '/_/version', // GET /_/version
1129
+ changelogPath: '/_/changelog', // GET /_/changelog
1130
+ }),
1131
+ ],
1132
+ })
1133
+ export class AppModule {}
1134
+ ```
1135
+
1136
+ This automatically provides three endpoints:
1137
+
1138
+ | Endpoint | Description | Source |
1139
+ |----------|-------------|--------|
1140
+ | `/_/health` | Returns `{ status: 'ok' }` for health checks | Built-in |
1141
+ | `/_/version` | Returns version from `package.json` | `package.json` |
1142
+ | `/_/changelog` | Returns changelog content | `CHANGELOG.md` |
1143
+
1144
+ ### Swagger/OpenAPI Documentation
1145
+
1146
+ Add API documentation using the PAE Service SDK:
1147
+
1148
+ <Tabs>
1149
+ <TabItem label="Setup in main.ts">
1150
+
1151
+ ```typescript
1152
+ import { NestFactory } from '@nestjs/core';
1153
+ import { AppModule } from './app.module';
1154
+ import { ApiDocsService } from '@bluealba/pae-service-nestjs-sdk';
1155
+ import packageJSON from '../package.json';
1156
+
1157
+ async function bootstrap() {
1158
+ const app = await NestFactory.create(AppModule);
1159
+
1160
+ // Setup Swagger
1161
+ const apiDocsService = app.get(ApiDocsService);
1162
+ apiDocsService.setup(app, {
1163
+ title: 'My Service API',
1164
+ description: 'API documentation for My Service',
1165
+ version: packageJSON.version,
1166
+ tags: ['Users', 'Products'],
1167
+ });
1168
+
1169
+ await app.listen(80);
1170
+ }
1171
+
1172
+ bootstrap();
1173
+ ```
1174
+
1175
+ </TabItem>
1176
+
1177
+ <TabItem label="Add to Module">
1178
+
1179
+ ```typescript
1180
+ import { Module } from '@nestjs/common';
1181
+ import { ApiDocsModule } from '@bluealba/pae-service-nestjs-sdk';
1182
+ import { UsersModule } from './users/users.module';
1183
+
1184
+ @Module({
1185
+ imports: [
1186
+ ApiDocsModule, // Required for API docs
1187
+ UsersModule,
1188
+ ],
1189
+ })
1190
+ export class AppModule {}
1191
+ ```
1192
+
1193
+ </TabItem>
1194
+
1195
+ <TabItem label="Document Endpoints">
1196
+
1197
+ ```typescript
1198
+ import { Controller, Get, Post, Body } from '@nestjs/common';
1199
+ import { ApiTags, ApiOperation, ApiResponse, ApiBody } from '@nestjs/swagger';
1200
+ import { UsersService } from './users.service';
1201
+ import { CreateUserDto } from './dto/create-user.dto';
1202
+
1203
+ @Controller('users')
1204
+ @ApiTags('users') // Group in Swagger UI
1205
+ export class UsersController {
1206
+ constructor(private readonly usersService: UsersService) {}
1207
+
1208
+ @Post()
1209
+ @ApiOperation({ summary: 'Create a new user' })
1210
+ @ApiBody({ type: CreateUserDto })
1211
+ @ApiResponse({ status: 201, description: 'User created successfully' })
1212
+ @ApiResponse({ status: 400, description: 'Invalid input' })
1213
+ create(@Body() createUserDto: CreateUserDto) {
1214
+ return this.usersService.create(createUserDto);
1215
+ }
1216
+
1217
+ @Get()
1218
+ @ApiOperation({ summary: 'Get all users' })
1219
+ @ApiResponse({ status: 200, description: 'List of users' })
1220
+ findAll() {
1221
+ return this.usersService.findAll();
1222
+ }
1223
+ }
1224
+ ```
1225
+
1226
+ </TabItem>
1227
+ </Tabs>
1228
+
1229
+ Swagger UI will be available at `http://localhost/api-docs` by default.
1230
+
1231
+ ### Authentication with Gateway
1232
+
1233
+ Services receive authenticated requests from the gateway with user context in headers:
1234
+
1235
+ ```typescript
1236
+ import { Controller, Get, Headers } from '@nestjs/common';
1237
+
1238
+ @Controller('profile')
1239
+ export class ProfileController {
1240
+ @Get()
1241
+ getProfile(@Headers('x-forwarded-user-id') userId: string) {
1242
+ // userId is extracted from JWT by gateway
1243
+ return { userId, message: 'User profile' };
1244
+ }
1245
+ }
1246
+ ```
1247
+
1248
+ The gateway sets these headers after authentication:
1249
+ - `x-forwarded-user-id`: User's unique identifier
1250
+ - `x-forwarded-user-email`: User's email
1251
+ - `x-forwarded-user-operations`: Comma-separated list of granted operations
1252
+
1253
+ ### Authorization
1254
+
1255
+ Use operations from `@bluealba/pae-core` for authorization:
1256
+
1257
+ ```typescript
1258
+ import { Controller, Get, Headers, ForbiddenException } from '@nestjs/common';
1259
+
1260
+ @Controller('users')
1261
+ export class UsersController {
1262
+ @Get('admin')
1263
+ getAdminData(@Headers('x-forwarded-user-operations') operations: string) {
1264
+ const userOps = operations?.split(',') || [];
1265
+
1266
+ if (!userOps.includes('users::admin::read')) {
1267
+ throw new ForbiddenException('Insufficient permissions');
1268
+ }
1269
+
1270
+ return { data: 'Admin data' };
1271
+ }
1272
+ }
1273
+ ```
1274
+
1275
+ ---
1276
+
1277
+ ## Catalog Registration
1278
+
1279
+ Services must register with the application catalog to be discovered by the gateway.
1280
+
1281
+ <Tabs>
1282
+ <TabItem label="Via API">
1283
+
1284
+ ```bash
1285
+ POST /api/catalog
1286
+ Content-Type: application/json
1287
+ Authorization: Bearer <admin-jwt>
1288
+
1289
+ {
1290
+ "name": "@myorg/my-service",
1291
+ "type": "service",
1292
+ "displayName": "My Service",
1293
+ "baseUrl": "/api/my-service",
1294
+ "host": "my-service",
1295
+ "port": 80,
1296
+ "authorization": {
1297
+ "operations": [
1298
+ "my-service::read",
1299
+ "my-service::write"
1300
+ ]
1301
+ }
1302
+ }
1303
+ ```
1304
+
1305
+ </TabItem>
1306
+
1307
+ <TabItem label="Via Bootstrap">
1308
+
1309
+ For automated deployments, include in bootstrap configuration:
1310
+
1311
+ ```json
1312
+ {
1313
+ {
1314
+ "name": "@myorg/my-service",
1315
+ "displayName": "My Service",
1316
+ "description": "My custom service",
1317
+ "type": "service",
1318
+ "baseUrl": "/api/my-service",
1319
+ "service": {
1320
+ "host": "my-service",
1321
+ "port": 80
1322
+ },
1323
+ "authorization": {
1324
+ "routes": [
1325
+ {
1326
+ pattern: '/api/v1/users',
1327
+ operations: ["my-service::read"],
1328
+ methods: ['GET'],
1329
+ }
1330
+ ]
1331
+ }
1332
+ }
1333
+ }
1334
+ ```
1335
+
1336
+ </TabItem>
1337
+
1338
+ <TabItem label="Configuration Fields">
1339
+
1340
+ | Field | Description | Required |
1341
+ |-------|-------------|----------|
1342
+ | `name` | Unique service identifier (use @scope/name format) | Yes |
1343
+ | `displayName` | Human-readable service name | Yes |
1344
+ | `type` | Always `"service"` for backend services | Yes |
1345
+ | `baseUrl` | API path prefix (e.g., `/api/my-service`) | Yes |
1346
+ | `host` | Docker service name or hostname | Yes |
1347
+ | `port` | Service port (usually 80 in Docker) | Yes |
1348
+ | `authorization.operations` | List of operations required by this service | No |
1349
+ | `authorization.routes` | List of operations that this service requires for each of the endpoints | No |
1350
+
1351
+ </TabItem>
1352
+ </Tabs>
1353
+
1354
+ ---
1355
+
1356
+ ## Best Practices
1357
+
1358
+ ### Code Organization
1359
+
1360
+ <CardGrid>
1361
+ <Card title="Feature-Based Modules" icon="folder">
1362
+ Organize code by business domain (users, products, orders) rather than technical layers.
1363
+ </Card>
1364
+
1365
+ <Card title="Single Responsibility" icon="document">
1366
+ Each service should handle one concern. Keep controllers thin and services focused.
1367
+ </Card>
1368
+
1369
+ <Card title="Dependency Injection" icon="puzzle">
1370
+ Use constructor injection for all dependencies. Avoid service locator pattern.
1371
+ </Card>
1372
+
1373
+ <Card title="Error Handling" icon="warning">
1374
+ Use NestJS built-in exceptions. Create custom exceptions for domain errors.
1375
+ </Card>
1376
+ </CardGrid>
1377
+
1378
+ **Recommended Module Structure:**
1379
+
1380
+ ```
1381
+ src/
1382
+ ├── users/
1383
+ │ ├── users.module.ts
1384
+ │ ├── users.controller.ts
1385
+ │ ├── users.service.ts
1386
+ │ ├── dto/
1387
+ │ │ ├── create-user.dto.ts
1388
+ │ │ └── update-user.dto.ts
1389
+ │ ├── entities/
1390
+ │ │ └── user.entity.ts
1391
+ │ └── tests/
1392
+ │ ├── users.controller.spec.ts
1393
+ │ └── users.service.spec.ts
1394
+ └── products/
1395
+ ├── products.module.ts
1396
+ ├── products.controller.ts
1397
+ ├── products.service.ts
1398
+ └── ...
1399
+ ```
1400
+
1401
+ ### Error Handling
1402
+
1403
+ ```typescript
1404
+ import {
1405
+ BadRequestException,
1406
+ NotFoundException,
1407
+ ConflictException,
1408
+ InternalServerErrorException
1409
+ } from '@nestjs/common';
1410
+
1411
+ @Injectable()
1412
+ export class UsersService {
1413
+ async findOne(id: number) {
1414
+ const user = await this.db.findById(id);
1415
+
1416
+ if (!user) {
1417
+ throw new NotFoundException(`User with ID ${id} not found`);
1418
+ }
1419
+
1420
+ return user;
1421
+ }
1422
+
1423
+ async create(dto: CreateUserDto) {
1424
+ const existing = await this.db.findByEmail(dto.email);
1425
+
1426
+ if (existing) {
1427
+ throw new ConflictException(`User with email ${dto.email} already exists`);
1428
+ }
1429
+
1430
+ try {
1431
+ return await this.db.create(dto);
1432
+ } catch (error) {
1433
+ throw new InternalServerErrorException('Failed to create user');
1434
+ }
1435
+ }
1436
+ }
1437
+ ```
1438
+
1439
+ ### Logging
1440
+
1441
+ ```typescript
1442
+ import { Injectable, Logger } from '@nestjs/common';
1443
+
1444
+ @Injectable()
1445
+ export class UsersService {
1446
+ private readonly logger = new Logger(UsersService.name);
1447
+
1448
+ async create(dto: CreateUserDto) {
1449
+ this.logger.log(`Creating user: ${dto.username}`);
1450
+
1451
+ try {
1452
+ const user = await this.db.create(dto);
1453
+ this.logger.log(`User created successfully: ${user.id}`);
1454
+ return user;
1455
+ } catch (error) {
1456
+ this.logger.error(`Failed to create user: ${error.message}`, error.stack);
1457
+ throw error;
1458
+ }
1459
+ }
1460
+ }
1461
+ ```
1462
+
1463
+ ### Testing
1464
+
1465
+ <Tabs>
1466
+ <TabItem label="Unit Test">
1467
+
1468
+ ```typescript
1469
+ import { Test, TestingModule } from '@nestjs/testing';
1470
+ import { UsersService } from './users.service';
1471
+ import { DatabaseService } from '../database/database.service';
1472
+
1473
+ describe('UsersService', () => {
1474
+ let service: UsersService;
1475
+ let dbService: DatabaseService;
1476
+
1477
+ beforeEach(async () => {
1478
+ const module: TestingModule = await Test.createTestingModule({
1479
+ providers: [
1480
+ UsersService,
1481
+ {
1482
+ provide: DatabaseService,
1483
+ useValue: {
1484
+ db: jest.fn(() => ({
1485
+ select: jest.fn().mockReturnThis(),
1486
+ where: jest.fn().mockResolvedValue([{ id: 1, username: 'test' }]),
1487
+ })),
1488
+ },
1489
+ },
1490
+ ],
1491
+ }).compile();
1492
+
1493
+ service = module.get<UsersService>(UsersService);
1494
+ dbService = module.get<DatabaseService>(DatabaseService);
1495
+ });
1496
+
1497
+ it('should be defined', () => {
1498
+ expect(service).toBeDefined();
1499
+ });
1500
+
1501
+ it('should find a user by id', async () => {
1502
+ const user = await service.findOne(1);
1503
+ expect(user).toEqual({ id: 1, username: 'test' });
1504
+ });
1505
+ });
1506
+ ```
1507
+
1508
+ </TabItem>
1509
+
1510
+ <TabItem label="Controller Test">
1511
+
1512
+ ```typescript
1513
+ import { Test, TestingModule } from '@nestjs/testing';
1514
+ import { UsersController } from './users.controller';
1515
+ import { UsersService } from './users.service';
1516
+
1517
+ describe('UsersController', () => {
1518
+ let controller: UsersController;
1519
+ let service: UsersService;
1520
+
1521
+ beforeEach(async () => {
1522
+ const module: TestingModule = await Test.createTestingModule({
1523
+ controllers: [UsersController],
1524
+ providers: [
1525
+ {
1526
+ provide: UsersService,
1527
+ useValue: {
1528
+ findAll: jest.fn().mockResolvedValue([{ id: 1, username: 'test' }]),
1529
+ findOne: jest.fn().mockResolvedValue({ id: 1, username: 'test' }),
1530
+ create: jest.fn().mockResolvedValue({ id: 1, username: 'test' }),
1531
+ },
1532
+ },
1533
+ ],
1534
+ }).compile();
1535
+
1536
+ controller = module.get<UsersController>(UsersController);
1537
+ service = module.get<UsersService>(UsersService);
1538
+ });
1539
+
1540
+ it('should return an array of users', async () => {
1541
+ const result = await controller.findAll();
1542
+ expect(result).toEqual([{ id: 1, username: 'test' }]);
1543
+ expect(service.findAll).toHaveBeenCalled();
1544
+ });
1545
+ });
1546
+ ```
1547
+
1548
+ </TabItem>
1549
+
1550
+ <TabItem label="E2E Test">
1551
+
1552
+ ```typescript
1553
+ import { Test, TestingModule } from '@nestjs/testing';
1554
+ import { INestApplication } from '@nestjs/common';
1555
+ import * as request from 'supertest';
1556
+ import { AppModule } from './../src/app.module';
1557
+
1558
+ describe('UsersController (e2e)', () => {
1559
+ let app: INestApplication;
1560
+
1561
+ beforeEach(async () => {
1562
+ const moduleFixture: TestingModule = await Test.createTestingModule({
1563
+ imports: [AppModule],
1564
+ }).compile();
1565
+
1566
+ app = moduleFixture.createNestApplication();
1567
+ await app.init();
1568
+ });
1569
+
1570
+ afterEach(async () => {
1571
+ await app.close();
1572
+ });
1573
+
1574
+ it('/users (GET)', () => {
1575
+ return request(app.getHttpServer())
1576
+ .get('/users')
1577
+ .expect(200)
1578
+ .expect((res) => {
1579
+ expect(Array.isArray(res.body)).toBe(true);
1580
+ });
1581
+ });
1582
+
1583
+ it('/users (POST)', () => {
1584
+ return request(app.getHttpServer())
1585
+ .post('/users')
1586
+ .send({ username: 'test', email: 'test@example.com' })
1587
+ .expect(201)
1588
+ .expect((res) => {
1589
+ expect(res.body).toHaveProperty('id');
1590
+ expect(res.body.username).toBe('test');
1591
+ });
1592
+ });
1593
+ });
1594
+ ```
1595
+
1596
+ </TabItem>
1597
+
1598
+ </Tabs>
1599
+
1600
+ ---
1601
+
1602
+ ## Troubleshooting
1603
+
1604
+ <Tabs>
1605
+ <TabItem label="Build Failures">
1606
+
1607
+ **Problem: Module not found errors**
1608
+
1609
+ ```
1610
+ Error: Cannot find module '@bluealba/pae-service-nestjs-sdk'
1611
+ ```
1612
+
1613
+ **Solution:**
1614
+ ```bash
1615
+ # Install missing dependencies
1616
+ npm install
1617
+
1618
+ # Clear node_modules and reinstall
1619
+ rm -rf node_modules package-lock.json
1620
+ npm install
1621
+ ```
1622
+
1623
+ ---
1624
+
1625
+ **Problem: TypeScript compilation errors**
1626
+
1627
+ ```
1628
+ error TS2304: Cannot find name 'Express'
1629
+ ```
1630
+
1631
+ **Solution:**
1632
+ ```bash
1633
+ # Install type definitions
1634
+ npm install --save-dev @types/node @types/express
1635
+
1636
+ # Rebuild
1637
+ npm run build
1638
+ ```
1639
+
1640
+ </TabItem>
1641
+
1642
+ <TabItem label="Runtime Errors">
1643
+
1644
+ **Problem: Service doesn't start**
1645
+
1646
+ ```
1647
+ Error: listen EADDRINUSE: address already in use :::80
1648
+ ```
1649
+
1650
+ **Solution:**
1651
+ ```bash
1652
+ # Check what's using port 80
1653
+ lsof -i :80
1654
+
1655
+ # Kill the process or change port
1656
+ PORT=3000 npm run start:dev
1657
+ ```
1658
+
1659
+ ---
1660
+
1661
+ **Problem: Database connection fails**
1662
+
1663
+ ```
1664
+ Error: connect ECONNREFUSED 127.0.0.1:5432
1665
+ ```
1666
+
1667
+ **Solution:**
1668
+ 1. Verify database is running:
1669
+ ```bash
1670
+ docker-compose ps postgres
1671
+ ```
1672
+
1673
+ 2. Check environment variables:
1674
+ ```bash
1675
+ echo $DATABASE_URL
1676
+ ```
1677
+
1678
+ 3. Test connection manually:
1679
+ ```bash
1680
+ psql -h localhost -U user -d mydb
1681
+ ```
1682
+
1683
+ </TabItem>
1684
+
1685
+ <TabItem label="Docker Issues">
1686
+
1687
+ **Problem: Container exits immediately**
1688
+
1689
+ ```bash
1690
+ # View logs
1691
+ docker-compose logs my-service
1692
+
1693
+ # Common causes:
1694
+ # - npm install failed
1695
+ # - Missing environment variables
1696
+ # - Syntax error in code
1697
+ ```
1698
+
1699
+ **Solution:**
1700
+ ```bash
1701
+ # Rebuild with no cache
1702
+ docker-compose build --no-cache my-service
1703
+
1704
+ # Run interactively to debug
1705
+ docker-compose run my-service sh
1706
+ ```
1707
+
1708
+ ---
1709
+
1710
+ **Problem: Code changes not reflected**
1711
+
1712
+ **Solution:**
1713
+ ```bash
1714
+ # Ensure volume mount is correct in docker-compose.yml
1715
+ volumes:
1716
+ - ../my-service:/app/out/apps/my-service
1717
+
1718
+ # Restart with rebuild
1719
+ docker-compose up --build my-service
1720
+ ```
1721
+
1722
+ </TabItem>
1723
+
1724
+ </Tabs>
1725
+
1726
+ ---
1727
+
1728
+ ## Related Documentation
1729
+
1730
+ - **[Architecture Overview](../architecture/overview)** - Understand platform architecture
1731
+ - **[Gateway Architecture](../architecture/gateway-architecture)** - Learn about the API gateway
1732
+ - **[Authentication System](../architecture/authentication-system)** - Understand authentication flows
1733
+ - **[Authorization System](../architecture/authorization-system)** - Master operations and RBAC
1734
+ - **[Multi-Tenancy](../architecture/multi-tenancy)** - Learn about tenant isolation
1735
+ - **[Creating UI Modules](./creating-ui-modules)** - Build frontend applications
1736
+ - **[Using Feature Flags](./using-feature-flags)** - Implement feature toggles