omgkit 2.1.0 → 2.2.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 (56) hide show
  1. package/package.json +1 -1
  2. package/plugin/skills/SKILL_STANDARDS.md +743 -0
  3. package/plugin/skills/databases/mongodb/SKILL.md +797 -28
  4. package/plugin/skills/databases/postgresql/SKILL.md +494 -18
  5. package/plugin/skills/databases/prisma/SKILL.md +776 -30
  6. package/plugin/skills/databases/redis/SKILL.md +885 -25
  7. package/plugin/skills/devops/aws/SKILL.md +686 -28
  8. package/plugin/skills/devops/docker/SKILL.md +466 -18
  9. package/plugin/skills/devops/github-actions/SKILL.md +684 -29
  10. package/plugin/skills/devops/kubernetes/SKILL.md +621 -24
  11. package/plugin/skills/frameworks/django/SKILL.md +920 -20
  12. package/plugin/skills/frameworks/express/SKILL.md +1361 -35
  13. package/plugin/skills/frameworks/fastapi/SKILL.md +1260 -33
  14. package/plugin/skills/frameworks/laravel/SKILL.md +1244 -31
  15. package/plugin/skills/frameworks/nestjs/SKILL.md +1005 -26
  16. package/plugin/skills/frameworks/nextjs/SKILL.md +407 -44
  17. package/plugin/skills/frameworks/rails/SKILL.md +594 -28
  18. package/plugin/skills/frameworks/react/SKILL.md +1006 -32
  19. package/plugin/skills/frameworks/spring/SKILL.md +528 -35
  20. package/plugin/skills/frameworks/vue/SKILL.md +1296 -27
  21. package/plugin/skills/frontend/accessibility/SKILL.md +1108 -34
  22. package/plugin/skills/frontend/frontend-design/SKILL.md +1304 -26
  23. package/plugin/skills/frontend/responsive/SKILL.md +847 -21
  24. package/plugin/skills/frontend/shadcn-ui/SKILL.md +976 -38
  25. package/plugin/skills/frontend/tailwindcss/SKILL.md +831 -35
  26. package/plugin/skills/frontend/threejs/SKILL.md +1298 -29
  27. package/plugin/skills/languages/javascript/SKILL.md +935 -31
  28. package/plugin/skills/languages/python/SKILL.md +489 -25
  29. package/plugin/skills/languages/typescript/SKILL.md +379 -30
  30. package/plugin/skills/methodology/brainstorming/SKILL.md +597 -23
  31. package/plugin/skills/methodology/defense-in-depth/SKILL.md +832 -34
  32. package/plugin/skills/methodology/dispatching-parallel-agents/SKILL.md +665 -31
  33. package/plugin/skills/methodology/executing-plans/SKILL.md +556 -24
  34. package/plugin/skills/methodology/finishing-development-branch/SKILL.md +595 -25
  35. package/plugin/skills/methodology/problem-solving/SKILL.md +429 -61
  36. package/plugin/skills/methodology/receiving-code-review/SKILL.md +536 -24
  37. package/plugin/skills/methodology/requesting-code-review/SKILL.md +632 -21
  38. package/plugin/skills/methodology/root-cause-tracing/SKILL.md +641 -30
  39. package/plugin/skills/methodology/sequential-thinking/SKILL.md +262 -3
  40. package/plugin/skills/methodology/systematic-debugging/SKILL.md +571 -32
  41. package/plugin/skills/methodology/test-driven-development/SKILL.md +779 -24
  42. package/plugin/skills/methodology/testing-anti-patterns/SKILL.md +691 -29
  43. package/plugin/skills/methodology/token-optimization/SKILL.md +598 -29
  44. package/plugin/skills/methodology/verification-before-completion/SKILL.md +543 -22
  45. package/plugin/skills/methodology/writing-plans/SKILL.md +590 -18
  46. package/plugin/skills/omega/omega-architecture/SKILL.md +838 -39
  47. package/plugin/skills/omega/omega-coding/SKILL.md +636 -39
  48. package/plugin/skills/omega/omega-sprint/SKILL.md +855 -48
  49. package/plugin/skills/omega/omega-testing/SKILL.md +940 -41
  50. package/plugin/skills/omega/omega-thinking/SKILL.md +703 -50
  51. package/plugin/skills/security/better-auth/SKILL.md +1065 -28
  52. package/plugin/skills/security/oauth/SKILL.md +968 -31
  53. package/plugin/skills/security/owasp/SKILL.md +894 -33
  54. package/plugin/skills/testing/playwright/SKILL.md +764 -38
  55. package/plugin/skills/testing/pytest/SKILL.md +873 -36
  56. package/plugin/skills/testing/vitest/SKILL.md +980 -35
@@ -1,67 +1,1046 @@
1
1
  ---
2
2
  name: nestjs
3
- description: NestJS development. Use for NestJS projects, modules, dependency injection.
3
+ description: Enterprise NestJS development with TypeScript, dependency injection, and microservices patterns
4
+ category: frameworks
5
+ triggers:
6
+ - nestjs
7
+ - nest.js
8
+ - nest
9
+ - node typescript
10
+ - typescript backend
11
+ - nodejs framework
12
+ - nestjs api
13
+ - nestjs microservices
4
14
  ---
5
15
 
6
- # NestJS Skill
16
+ # NestJS
7
17
 
8
- ## Patterns
18
+ Enterprise-grade **NestJS development** following industry best practices. This skill covers modular architecture, dependency injection, TypeORM integration, authentication with Passport, testing patterns, and microservices configurations used by top engineering teams.
19
+
20
+ ## Purpose
21
+
22
+ Build scalable Node.js applications with confidence:
23
+
24
+ - Design modular, maintainable architectures
25
+ - Implement robust dependency injection
26
+ - Handle authentication and authorization
27
+ - Validate requests with class-validator
28
+ - Write comprehensive tests with Jest
29
+ - Deploy production-ready applications
30
+ - Build microservices and message queues
31
+
32
+ ## Features
33
+
34
+ ### 1. Module Architecture
9
35
 
10
- ### Module
11
36
  ```typescript
37
+ // src/app.module.ts
38
+ import { Module, MiddlewareConsumer, NestModule } from '@nestjs/common';
39
+ import { ConfigModule, ConfigService } from '@nestjs/config';
40
+ import { TypeOrmModule } from '@nestjs/typeorm';
41
+ import { ThrottlerModule } from '@nestjs/throttler';
42
+ import { APP_GUARD, APP_INTERCEPTOR } from '@nestjs/core';
43
+
44
+ import { AuthModule } from './auth/auth.module';
45
+ import { UsersModule } from './users/users.module';
46
+ import { OrganizationsModule } from './organizations/organizations.module';
47
+ import { ProjectsModule } from './projects/projects.module';
48
+ import { LoggingInterceptor } from './common/interceptors/logging.interceptor';
49
+ import { RequestIdMiddleware } from './common/middleware/request-id.middleware';
50
+ import configuration from './config/configuration';
51
+
12
52
  @Module({
13
- imports: [DatabaseModule],
53
+ imports: [
54
+ ConfigModule.forRoot({
55
+ isGlobal: true,
56
+ load: [configuration],
57
+ }),
58
+ TypeOrmModule.forRootAsync({
59
+ imports: [ConfigModule],
60
+ inject: [ConfigService],
61
+ useFactory: (config: ConfigService) => ({
62
+ type: 'postgres',
63
+ host: config.get('database.host'),
64
+ port: config.get('database.port'),
65
+ username: config.get('database.username'),
66
+ password: config.get('database.password'),
67
+ database: config.get('database.name'),
68
+ entities: [__dirname + '/**/*.entity{.ts,.js}'],
69
+ synchronize: config.get('database.synchronize'),
70
+ logging: config.get('database.logging'),
71
+ }),
72
+ }),
73
+ ThrottlerModule.forRoot([{
74
+ ttl: 60000,
75
+ limit: 100,
76
+ }]),
77
+ AuthModule,
78
+ UsersModule,
79
+ OrganizationsModule,
80
+ ProjectsModule,
81
+ ],
82
+ providers: [
83
+ {
84
+ provide: APP_INTERCEPTOR,
85
+ useClass: LoggingInterceptor,
86
+ },
87
+ ],
88
+ })
89
+ export class AppModule implements NestModule {
90
+ configure(consumer: MiddlewareConsumer) {
91
+ consumer.apply(RequestIdMiddleware).forRoutes('*');
92
+ }
93
+ }
94
+
95
+
96
+ // src/users/users.module.ts
97
+ import { Module } from '@nestjs/common';
98
+ import { TypeOrmModule } from '@nestjs/typeorm';
99
+ import { UsersController } from './users.controller';
100
+ import { UsersService } from './users.service';
101
+ import { User } from './entities/user.entity';
102
+ import { UsersRepository } from './users.repository';
103
+
104
+ @Module({
105
+ imports: [TypeOrmModule.forFeature([User])],
14
106
  controllers: [UsersController],
15
- providers: [UsersService],
107
+ providers: [UsersService, UsersRepository],
16
108
  exports: [UsersService],
17
109
  })
18
110
  export class UsersModule {}
111
+
112
+
113
+ // src/config/configuration.ts
114
+ export default () => ({
115
+ port: parseInt(process.env.PORT, 10) || 3000,
116
+ database: {
117
+ host: process.env.DB_HOST || 'localhost',
118
+ port: parseInt(process.env.DB_PORT, 10) || 5432,
119
+ username: process.env.DB_USERNAME || 'postgres',
120
+ password: process.env.DB_PASSWORD,
121
+ name: process.env.DB_NAME || 'app',
122
+ synchronize: process.env.NODE_ENV !== 'production',
123
+ logging: process.env.NODE_ENV === 'development',
124
+ },
125
+ jwt: {
126
+ secret: process.env.JWT_SECRET,
127
+ expiresIn: process.env.JWT_EXPIRES_IN || '1h',
128
+ },
129
+ redis: {
130
+ host: process.env.REDIS_HOST || 'localhost',
131
+ port: parseInt(process.env.REDIS_PORT, 10) || 6379,
132
+ },
133
+ });
134
+ ```
135
+
136
+ ### 2. Entities and Repositories
137
+
138
+ ```typescript
139
+ // src/users/entities/user.entity.ts
140
+ import {
141
+ Entity,
142
+ PrimaryGeneratedColumn,
143
+ Column,
144
+ CreateDateColumn,
145
+ UpdateDateColumn,
146
+ DeleteDateColumn,
147
+ ManyToMany,
148
+ OneToMany,
149
+ BeforeInsert,
150
+ } from 'typeorm';
151
+ import * as bcrypt from 'bcrypt';
152
+ import { Organization } from '../../organizations/entities/organization.entity';
153
+ import { Exclude } from 'class-transformer';
154
+
155
+ export enum UserRole {
156
+ ADMIN = 'admin',
157
+ USER = 'user',
158
+ GUEST = 'guest',
159
+ }
160
+
161
+ @Entity('users')
162
+ export class User {
163
+ @PrimaryGeneratedColumn('uuid')
164
+ id: string;
165
+
166
+ @Column({ unique: true })
167
+ email: string;
168
+
169
+ @Column()
170
+ name: string;
171
+
172
+ @Column()
173
+ @Exclude()
174
+ password: string;
175
+
176
+ @Column({
177
+ type: 'enum',
178
+ enum: UserRole,
179
+ default: UserRole.USER,
180
+ })
181
+ role: UserRole;
182
+
183
+ @Column({ default: true })
184
+ isActive: boolean;
185
+
186
+ @CreateDateColumn()
187
+ createdAt: Date;
188
+
189
+ @UpdateDateColumn()
190
+ updatedAt: Date;
191
+
192
+ @DeleteDateColumn()
193
+ deletedAt?: Date;
194
+
195
+ @ManyToMany(() => Organization, (org) => org.members)
196
+ organizations: Organization[];
197
+
198
+ @OneToMany(() => Organization, (org) => org.owner)
199
+ ownedOrganizations: Organization[];
200
+
201
+ @BeforeInsert()
202
+ async hashPassword() {
203
+ if (this.password) {
204
+ this.password = await bcrypt.hash(this.password, 10);
205
+ }
206
+ }
207
+
208
+ async validatePassword(password: string): Promise<boolean> {
209
+ return bcrypt.compare(password, this.password);
210
+ }
211
+ }
212
+
213
+
214
+ // src/users/users.repository.ts
215
+ import { Injectable } from '@nestjs/common';
216
+ import { DataSource, Repository } from 'typeorm';
217
+ import { User, UserRole } from './entities/user.entity';
218
+
219
+ interface FindAllOptions {
220
+ search?: string;
221
+ role?: UserRole;
222
+ isActive?: boolean;
223
+ skip?: number;
224
+ take?: number;
225
+ }
226
+
227
+ @Injectable()
228
+ export class UsersRepository extends Repository<User> {
229
+ constructor(private dataSource: DataSource) {
230
+ super(User, dataSource.createEntityManager());
231
+ }
232
+
233
+ async findByEmail(email: string): Promise<User | null> {
234
+ return this.findOne({
235
+ where: { email },
236
+ });
237
+ }
238
+
239
+ async findAllWithCount(options: FindAllOptions): Promise<[User[], number]> {
240
+ const query = this.createQueryBuilder('user')
241
+ .where('user.deletedAt IS NULL');
242
+
243
+ if (options.search) {
244
+ query.andWhere(
245
+ '(user.name ILIKE :search OR user.email ILIKE :search)',
246
+ { search: `%${options.search}%` },
247
+ );
248
+ }
249
+
250
+ if (options.role) {
251
+ query.andWhere('user.role = :role', { role: options.role });
252
+ }
253
+
254
+ if (options.isActive !== undefined) {
255
+ query.andWhere('user.isActive = :isActive', { isActive: options.isActive });
256
+ }
257
+
258
+ return query
259
+ .orderBy('user.createdAt', 'DESC')
260
+ .skip(options.skip || 0)
261
+ .take(options.take || 20)
262
+ .getManyAndCount();
263
+ }
264
+
265
+ async findWithOrganizations(id: string): Promise<User | null> {
266
+ return this.findOne({
267
+ where: { id },
268
+ relations: ['organizations'],
269
+ });
270
+ }
271
+ }
19
272
  ```
20
273
 
21
- ### Controller
274
+ ### 3. DTOs and Validation
275
+
22
276
  ```typescript
277
+ // src/users/dto/create-user.dto.ts
278
+ import {
279
+ IsEmail,
280
+ IsString,
281
+ IsEnum,
282
+ IsOptional,
283
+ MinLength,
284
+ MaxLength,
285
+ Matches,
286
+ } from 'class-validator';
287
+ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
288
+ import { UserRole } from '../entities/user.entity';
289
+
290
+ export class CreateUserDto {
291
+ @ApiProperty({ example: 'user@example.com' })
292
+ @IsEmail()
293
+ email: string;
294
+
295
+ @ApiProperty({ example: 'John Doe' })
296
+ @IsString()
297
+ @MinLength(2)
298
+ @MaxLength(100)
299
+ name: string;
300
+
301
+ @ApiProperty({ example: 'SecurePass123!' })
302
+ @IsString()
303
+ @MinLength(8)
304
+ @MaxLength(128)
305
+ @Matches(
306
+ /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]/,
307
+ { message: 'Password must contain uppercase, lowercase, number and special character' },
308
+ )
309
+ password: string;
310
+
311
+ @ApiPropertyOptional({ enum: UserRole })
312
+ @IsOptional()
313
+ @IsEnum(UserRole)
314
+ role?: UserRole;
315
+ }
316
+
317
+
318
+ // src/users/dto/update-user.dto.ts
319
+ import { PartialType, OmitType } from '@nestjs/swagger';
320
+ import { CreateUserDto } from './create-user.dto';
321
+ import { IsBoolean, IsOptional } from 'class-validator';
322
+
323
+ export class UpdateUserDto extends PartialType(
324
+ OmitType(CreateUserDto, ['password'] as const),
325
+ ) {
326
+ @IsOptional()
327
+ @IsBoolean()
328
+ isActive?: boolean;
329
+ }
330
+
331
+
332
+ // src/users/dto/user-response.dto.ts
333
+ import { ApiProperty } from '@nestjs/swagger';
334
+ import { Expose, Type } from 'class-transformer';
335
+ import { UserRole } from '../entities/user.entity';
336
+
337
+ export class UserResponseDto {
338
+ @ApiProperty()
339
+ @Expose()
340
+ id: string;
341
+
342
+ @ApiProperty()
343
+ @Expose()
344
+ email: string;
345
+
346
+ @ApiProperty()
347
+ @Expose()
348
+ name: string;
349
+
350
+ @ApiProperty({ enum: UserRole })
351
+ @Expose()
352
+ role: UserRole;
353
+
354
+ @ApiProperty()
355
+ @Expose()
356
+ isActive: boolean;
357
+
358
+ @ApiProperty()
359
+ @Expose()
360
+ createdAt: Date;
361
+
362
+ @ApiProperty()
363
+ @Expose()
364
+ updatedAt: Date;
365
+ }
366
+
367
+
368
+ // src/common/dto/pagination.dto.ts
369
+ import { ApiPropertyOptional } from '@nestjs/swagger';
370
+ import { IsOptional, IsInt, Min, Max } from 'class-validator';
371
+ import { Type } from 'class-transformer';
372
+
373
+ export class PaginationDto {
374
+ @ApiPropertyOptional({ default: 1 })
375
+ @IsOptional()
376
+ @Type(() => Number)
377
+ @IsInt()
378
+ @Min(1)
379
+ page?: number = 1;
380
+
381
+ @ApiPropertyOptional({ default: 20 })
382
+ @IsOptional()
383
+ @Type(() => Number)
384
+ @IsInt()
385
+ @Min(1)
386
+ @Max(100)
387
+ limit?: number = 20;
388
+
389
+ get skip(): number {
390
+ return (this.page - 1) * this.limit;
391
+ }
392
+ }
393
+
394
+ export class PaginatedResponseDto<T> {
395
+ @ApiProperty()
396
+ data: T[];
397
+
398
+ @ApiProperty()
399
+ total: number;
400
+
401
+ @ApiProperty()
402
+ page: number;
403
+
404
+ @ApiProperty()
405
+ limit: number;
406
+
407
+ @ApiProperty()
408
+ totalPages: number;
409
+
410
+ @ApiProperty()
411
+ hasMore: boolean;
412
+
413
+ static create<T>(
414
+ data: T[],
415
+ total: number,
416
+ page: number,
417
+ limit: number,
418
+ ): PaginatedResponseDto<T> {
419
+ const totalPages = Math.ceil(total / limit);
420
+ return {
421
+ data,
422
+ total,
423
+ page,
424
+ limit,
425
+ totalPages,
426
+ hasMore: page < totalPages,
427
+ };
428
+ }
429
+ }
430
+ ```
431
+
432
+ ### 4. Controllers
433
+
434
+ ```typescript
435
+ // src/users/users.controller.ts
436
+ import {
437
+ Controller,
438
+ Get,
439
+ Post,
440
+ Patch,
441
+ Delete,
442
+ Body,
443
+ Param,
444
+ Query,
445
+ UseGuards,
446
+ ParseUUIDPipe,
447
+ HttpCode,
448
+ HttpStatus,
449
+ } from '@nestjs/common';
450
+ import {
451
+ ApiTags,
452
+ ApiOperation,
453
+ ApiResponse,
454
+ ApiBearerAuth,
455
+ } from '@nestjs/swagger';
456
+ import { UsersService } from './users.service';
457
+ import { CreateUserDto } from './dto/create-user.dto';
458
+ import { UpdateUserDto } from './dto/update-user.dto';
459
+ import { UserResponseDto } from './dto/user-response.dto';
460
+ import { PaginationDto, PaginatedResponseDto } from '../common/dto/pagination.dto';
461
+ import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
462
+ import { RolesGuard } from '../auth/guards/roles.guard';
463
+ import { Roles } from '../auth/decorators/roles.decorator';
464
+ import { CurrentUser } from '../auth/decorators/current-user.decorator';
465
+ import { User, UserRole } from './entities/user.entity';
466
+
467
+ @ApiTags('users')
23
468
  @Controller('users')
469
+ @UseGuards(JwtAuthGuard, RolesGuard)
470
+ @ApiBearerAuth()
24
471
  export class UsersController {
25
- constructor(private usersService: UsersService) {}
472
+ constructor(private readonly usersService: UsersService) {}
26
473
 
27
474
  @Get()
28
- findAll() {
29
- return this.usersService.findAll();
475
+ @Roles(UserRole.ADMIN)
476
+ @ApiOperation({ summary: 'List all users' })
477
+ @ApiResponse({ status: 200, type: PaginatedResponseDto })
478
+ async findAll(
479
+ @Query() pagination: PaginationDto,
480
+ @Query('search') search?: string,
481
+ @Query('role') role?: UserRole,
482
+ ): Promise<PaginatedResponseDto<UserResponseDto>> {
483
+ return this.usersService.findAll(pagination, { search, role });
484
+ }
485
+
486
+ @Get('me')
487
+ @ApiOperation({ summary: 'Get current user profile' })
488
+ @ApiResponse({ status: 200, type: UserResponseDto })
489
+ async getProfile(@CurrentUser() user: User): Promise<UserResponseDto> {
490
+ return this.usersService.findOne(user.id);
491
+ }
492
+
493
+ @Patch('me')
494
+ @ApiOperation({ summary: 'Update current user profile' })
495
+ @ApiResponse({ status: 200, type: UserResponseDto })
496
+ async updateProfile(
497
+ @CurrentUser() user: User,
498
+ @Body() updateUserDto: UpdateUserDto,
499
+ ): Promise<UserResponseDto> {
500
+ return this.usersService.update(user.id, updateUserDto);
501
+ }
502
+
503
+ @Get(':id')
504
+ @Roles(UserRole.ADMIN)
505
+ @ApiOperation({ summary: 'Get user by ID' })
506
+ @ApiResponse({ status: 200, type: UserResponseDto })
507
+ async findOne(
508
+ @Param('id', ParseUUIDPipe) id: string,
509
+ ): Promise<UserResponseDto> {
510
+ return this.usersService.findOne(id);
30
511
  }
31
512
 
32
513
  @Post()
33
- create(@Body() dto: CreateUserDto) {
34
- return this.usersService.create(dto);
514
+ @Roles(UserRole.ADMIN)
515
+ @ApiOperation({ summary: 'Create new user' })
516
+ @ApiResponse({ status: 201, type: UserResponseDto })
517
+ async create(@Body() createUserDto: CreateUserDto): Promise<UserResponseDto> {
518
+ return this.usersService.create(createUserDto);
519
+ }
520
+
521
+ @Patch(':id')
522
+ @Roles(UserRole.ADMIN)
523
+ @ApiOperation({ summary: 'Update user' })
524
+ @ApiResponse({ status: 200, type: UserResponseDto })
525
+ async update(
526
+ @Param('id', ParseUUIDPipe) id: string,
527
+ @Body() updateUserDto: UpdateUserDto,
528
+ ): Promise<UserResponseDto> {
529
+ return this.usersService.update(id, updateUserDto);
530
+ }
531
+
532
+ @Delete(':id')
533
+ @Roles(UserRole.ADMIN)
534
+ @HttpCode(HttpStatus.NO_CONTENT)
535
+ @ApiOperation({ summary: 'Delete user' })
536
+ @ApiResponse({ status: 204 })
537
+ async remove(@Param('id', ParseUUIDPipe) id: string): Promise<void> {
538
+ await this.usersService.remove(id);
35
539
  }
36
540
  }
37
541
  ```
38
542
 
39
- ### Service
543
+ ### 5. Services
544
+
40
545
  ```typescript
546
+ // src/users/users.service.ts
547
+ import {
548
+ Injectable,
549
+ NotFoundException,
550
+ ConflictException,
551
+ } from '@nestjs/common';
552
+ import { plainToInstance } from 'class-transformer';
553
+ import { UsersRepository } from './users.repository';
554
+ import { CreateUserDto } from './dto/create-user.dto';
555
+ import { UpdateUserDto } from './dto/update-user.dto';
556
+ import { UserResponseDto } from './dto/user-response.dto';
557
+ import { PaginationDto, PaginatedResponseDto } from '../common/dto/pagination.dto';
558
+ import { User, UserRole } from './entities/user.entity';
559
+
560
+ interface FindAllFilters {
561
+ search?: string;
562
+ role?: UserRole;
563
+ isActive?: boolean;
564
+ }
565
+
41
566
  @Injectable()
42
567
  export class UsersService {
43
- constructor(private db: DatabaseService) {}
568
+ constructor(private readonly usersRepository: UsersRepository) {}
569
+
570
+ async findAll(
571
+ pagination: PaginationDto,
572
+ filters: FindAllFilters = {},
573
+ ): Promise<PaginatedResponseDto<UserResponseDto>> {
574
+ const [users, total] = await this.usersRepository.findAllWithCount({
575
+ ...filters,
576
+ skip: pagination.skip,
577
+ take: pagination.limit,
578
+ });
579
+
580
+ const data = users.map((user) =>
581
+ plainToInstance(UserResponseDto, user, { excludeExtraneousValues: true }),
582
+ );
583
+
584
+ return PaginatedResponseDto.create(
585
+ data,
586
+ total,
587
+ pagination.page,
588
+ pagination.limit,
589
+ );
590
+ }
591
+
592
+ async findOne(id: string): Promise<UserResponseDto> {
593
+ const user = await this.usersRepository.findOne({ where: { id } });
594
+
595
+ if (!user) {
596
+ throw new NotFoundException(`User with ID ${id} not found`);
597
+ }
598
+
599
+ return plainToInstance(UserResponseDto, user, {
600
+ excludeExtraneousValues: true,
601
+ });
602
+ }
603
+
604
+ async findByEmail(email: string): Promise<User | null> {
605
+ return this.usersRepository.findByEmail(email);
606
+ }
607
+
608
+ async create(createUserDto: CreateUserDto): Promise<UserResponseDto> {
609
+ const existing = await this.usersRepository.findByEmail(createUserDto.email);
610
+
611
+ if (existing) {
612
+ throw new ConflictException('Email already in use');
613
+ }
614
+
615
+ const user = this.usersRepository.create(createUserDto);
616
+ await this.usersRepository.save(user);
44
617
 
45
- async findAll() {
46
- return this.db.users.findMany();
618
+ return plainToInstance(UserResponseDto, user, {
619
+ excludeExtraneousValues: true,
620
+ });
621
+ }
622
+
623
+ async update(id: string, updateUserDto: UpdateUserDto): Promise<UserResponseDto> {
624
+ const user = await this.usersRepository.findOne({ where: { id } });
625
+
626
+ if (!user) {
627
+ throw new NotFoundException(`User with ID ${id} not found`);
628
+ }
629
+
630
+ if (updateUserDto.email && updateUserDto.email !== user.email) {
631
+ const existing = await this.usersRepository.findByEmail(updateUserDto.email);
632
+ if (existing) {
633
+ throw new ConflictException('Email already in use');
634
+ }
635
+ }
636
+
637
+ Object.assign(user, updateUserDto);
638
+ await this.usersRepository.save(user);
639
+
640
+ return plainToInstance(UserResponseDto, user, {
641
+ excludeExtraneousValues: true,
642
+ });
643
+ }
644
+
645
+ async remove(id: string): Promise<void> {
646
+ const user = await this.usersRepository.findOne({ where: { id } });
647
+
648
+ if (!user) {
649
+ throw new NotFoundException(`User with ID ${id} not found`);
650
+ }
651
+
652
+ await this.usersRepository.softRemove(user);
653
+ }
654
+
655
+ async validateUser(email: string, password: string): Promise<User | null> {
656
+ const user = await this.usersRepository.findByEmail(email);
657
+
658
+ if (!user || !user.isActive) {
659
+ return null;
660
+ }
661
+
662
+ const isValid = await user.validatePassword(password);
663
+ return isValid ? user : null;
47
664
  }
48
665
  }
49
666
  ```
50
667
 
51
- ### DTO
668
+ ### 6. Authentication
669
+
52
670
  ```typescript
53
- export class CreateUserDto {
54
- @IsEmail()
671
+ // src/auth/auth.module.ts
672
+ import { Module } from '@nestjs/common';
673
+ import { JwtModule } from '@nestjs/jwt';
674
+ import { PassportModule } from '@nestjs/passport';
675
+ import { ConfigModule, ConfigService } from '@nestjs/config';
676
+ import { AuthController } from './auth.controller';
677
+ import { AuthService } from './auth.service';
678
+ import { JwtStrategy } from './strategies/jwt.strategy';
679
+ import { LocalStrategy } from './strategies/local.strategy';
680
+ import { UsersModule } from '../users/users.module';
681
+
682
+ @Module({
683
+ imports: [
684
+ UsersModule,
685
+ PassportModule,
686
+ JwtModule.registerAsync({
687
+ imports: [ConfigModule],
688
+ inject: [ConfigService],
689
+ useFactory: (config: ConfigService) => ({
690
+ secret: config.get('jwt.secret'),
691
+ signOptions: { expiresIn: config.get('jwt.expiresIn') },
692
+ }),
693
+ }),
694
+ ],
695
+ controllers: [AuthController],
696
+ providers: [AuthService, JwtStrategy, LocalStrategy],
697
+ exports: [AuthService],
698
+ })
699
+ export class AuthModule {}
700
+
701
+
702
+ // src/auth/strategies/jwt.strategy.ts
703
+ import { Injectable, UnauthorizedException } from '@nestjs/common';
704
+ import { PassportStrategy } from '@nestjs/passport';
705
+ import { ExtractJwt, Strategy } from 'passport-jwt';
706
+ import { ConfigService } from '@nestjs/config';
707
+ import { UsersService } from '../../users/users.service';
708
+
709
+ interface JwtPayload {
710
+ sub: string;
55
711
  email: string;
712
+ role: string;
713
+ }
56
714
 
57
- @IsString()
58
- @MinLength(8)
59
- password: string;
715
+ @Injectable()
716
+ export class JwtStrategy extends PassportStrategy(Strategy) {
717
+ constructor(
718
+ private readonly configService: ConfigService,
719
+ private readonly usersService: UsersService,
720
+ ) {
721
+ super({
722
+ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
723
+ ignoreExpiration: false,
724
+ secretOrKey: configService.get('jwt.secret'),
725
+ });
726
+ }
727
+
728
+ async validate(payload: JwtPayload) {
729
+ const user = await this.usersService.findByEmail(payload.email);
730
+
731
+ if (!user || !user.isActive) {
732
+ throw new UnauthorizedException();
733
+ }
734
+
735
+ return user;
736
+ }
737
+ }
738
+
739
+
740
+ // src/auth/guards/roles.guard.ts
741
+ import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
742
+ import { Reflector } from '@nestjs/core';
743
+ import { UserRole } from '../../users/entities/user.entity';
744
+ import { ROLES_KEY } from '../decorators/roles.decorator';
745
+
746
+ @Injectable()
747
+ export class RolesGuard implements CanActivate {
748
+ constructor(private reflector: Reflector) {}
749
+
750
+ canActivate(context: ExecutionContext): boolean {
751
+ const requiredRoles = this.reflector.getAllAndOverride<UserRole[]>(
752
+ ROLES_KEY,
753
+ [context.getHandler(), context.getClass()],
754
+ );
755
+
756
+ if (!requiredRoles) {
757
+ return true;
758
+ }
759
+
760
+ const { user } = context.switchToHttp().getRequest();
761
+ return requiredRoles.includes(user.role);
762
+ }
763
+ }
764
+
765
+
766
+ // src/auth/decorators/roles.decorator.ts
767
+ import { SetMetadata } from '@nestjs/common';
768
+ import { UserRole } from '../../users/entities/user.entity';
769
+
770
+ export const ROLES_KEY = 'roles';
771
+ export const Roles = (...roles: UserRole[]) => SetMetadata(ROLES_KEY, roles);
772
+
773
+
774
+ // src/auth/decorators/current-user.decorator.ts
775
+ import { createParamDecorator, ExecutionContext } from '@nestjs/common';
776
+
777
+ export const CurrentUser = createParamDecorator(
778
+ (data: unknown, ctx: ExecutionContext) => {
779
+ const request = ctx.switchToHttp().getRequest();
780
+ return request.user;
781
+ },
782
+ );
783
+ ```
784
+
785
+ ### 7. Testing
786
+
787
+ ```typescript
788
+ // src/users/users.service.spec.ts
789
+ import { Test, TestingModule } from '@nestjs/testing';
790
+ import { NotFoundException, ConflictException } from '@nestjs/common';
791
+ import { UsersService } from './users.service';
792
+ import { UsersRepository } from './users.repository';
793
+ import { User, UserRole } from './entities/user.entity';
794
+
795
+ describe('UsersService', () => {
796
+ let service: UsersService;
797
+ let repository: jest.Mocked<UsersRepository>;
798
+
799
+ const mockUser: Partial<User> = {
800
+ id: 'test-id',
801
+ email: 'test@example.com',
802
+ name: 'Test User',
803
+ role: UserRole.USER,
804
+ isActive: true,
805
+ createdAt: new Date(),
806
+ updatedAt: new Date(),
807
+ };
808
+
809
+ beforeEach(async () => {
810
+ const module: TestingModule = await Test.createTestingModule({
811
+ providers: [
812
+ UsersService,
813
+ {
814
+ provide: UsersRepository,
815
+ useValue: {
816
+ findOne: jest.fn(),
817
+ findByEmail: jest.fn(),
818
+ findAllWithCount: jest.fn(),
819
+ create: jest.fn(),
820
+ save: jest.fn(),
821
+ softRemove: jest.fn(),
822
+ },
823
+ },
824
+ ],
825
+ }).compile();
826
+
827
+ service = module.get<UsersService>(UsersService);
828
+ repository = module.get(UsersRepository);
829
+ });
830
+
831
+ describe('findOne', () => {
832
+ it('should return a user', async () => {
833
+ repository.findOne.mockResolvedValue(mockUser as User);
834
+
835
+ const result = await service.findOne('test-id');
836
+
837
+ expect(result.email).toBe(mockUser.email);
838
+ expect(repository.findOne).toHaveBeenCalledWith({
839
+ where: { id: 'test-id' },
840
+ });
841
+ });
842
+
843
+ it('should throw NotFoundException if user not found', async () => {
844
+ repository.findOne.mockResolvedValue(null);
845
+
846
+ await expect(service.findOne('test-id')).rejects.toThrow(NotFoundException);
847
+ });
848
+ });
849
+
850
+ describe('create', () => {
851
+ it('should create a new user', async () => {
852
+ repository.findByEmail.mockResolvedValue(null);
853
+ repository.create.mockReturnValue(mockUser as User);
854
+ repository.save.mockResolvedValue(mockUser as User);
855
+
856
+ const result = await service.create({
857
+ email: 'test@example.com',
858
+ name: 'Test User',
859
+ password: 'Password123!',
860
+ });
861
+
862
+ expect(result.email).toBe(mockUser.email);
863
+ expect(repository.save).toHaveBeenCalled();
864
+ });
865
+
866
+ it('should throw ConflictException for duplicate email', async () => {
867
+ repository.findByEmail.mockResolvedValue(mockUser as User);
868
+
869
+ await expect(
870
+ service.create({
871
+ email: 'test@example.com',
872
+ name: 'Test',
873
+ password: 'Password123!',
874
+ }),
875
+ ).rejects.toThrow(ConflictException);
876
+ });
877
+ });
878
+ });
879
+
880
+
881
+ // src/users/users.controller.spec.ts
882
+ import { Test, TestingModule } from '@nestjs/testing';
883
+ import { UsersController } from './users.controller';
884
+ import { UsersService } from './users.service';
885
+
886
+ describe('UsersController', () => {
887
+ let controller: UsersController;
888
+ let service: jest.Mocked<UsersService>;
889
+
890
+ beforeEach(async () => {
891
+ const module: TestingModule = await Test.createTestingModule({
892
+ controllers: [UsersController],
893
+ providers: [
894
+ {
895
+ provide: UsersService,
896
+ useValue: {
897
+ findAll: jest.fn(),
898
+ findOne: jest.fn(),
899
+ create: jest.fn(),
900
+ update: jest.fn(),
901
+ remove: jest.fn(),
902
+ },
903
+ },
904
+ ],
905
+ }).compile();
906
+
907
+ controller = module.get<UsersController>(UsersController);
908
+ service = module.get(UsersService);
909
+ });
910
+
911
+ it('should be defined', () => {
912
+ expect(controller).toBeDefined();
913
+ });
914
+ });
915
+
916
+
917
+ // test/users.e2e-spec.ts
918
+ import { Test, TestingModule } from '@nestjs/testing';
919
+ import { INestApplication, ValidationPipe } from '@nestjs/common';
920
+ import * as request from 'supertest';
921
+ import { AppModule } from '../src/app.module';
922
+
923
+ describe('Users (e2e)', () => {
924
+ let app: INestApplication;
925
+ let authToken: string;
926
+
927
+ beforeAll(async () => {
928
+ const moduleFixture: TestingModule = await Test.createTestingModule({
929
+ imports: [AppModule],
930
+ }).compile();
931
+
932
+ app = moduleFixture.createNestApplication();
933
+ app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
934
+ await app.init();
935
+
936
+ // Get auth token
937
+ const loginResponse = await request(app.getHttpServer())
938
+ .post('/auth/login')
939
+ .send({ email: 'admin@test.com', password: 'Admin123!' });
940
+
941
+ authToken = loginResponse.body.access_token;
942
+ });
943
+
944
+ afterAll(async () => {
945
+ await app.close();
946
+ });
947
+
948
+ describe('/users (GET)', () => {
949
+ it('should return paginated users', () => {
950
+ return request(app.getHttpServer())
951
+ .get('/users')
952
+ .set('Authorization', `Bearer ${authToken}`)
953
+ .expect(200)
954
+ .expect((res) => {
955
+ expect(res.body).toHaveProperty('data');
956
+ expect(res.body).toHaveProperty('pagination');
957
+ });
958
+ });
959
+
960
+ it('should return 401 without auth token', () => {
961
+ return request(app.getHttpServer())
962
+ .get('/users')
963
+ .expect(401);
964
+ });
965
+ });
966
+ });
967
+ ```
968
+
969
+ ## Use Cases
970
+
971
+ ### Microservices with Message Queue
972
+
973
+ ```typescript
974
+ // src/app.module.ts (Microservice)
975
+ import { Module } from '@nestjs/common';
976
+ import { ClientsModule, Transport } from '@nestjs/microservices';
977
+
978
+ @Module({
979
+ imports: [
980
+ ClientsModule.register([
981
+ {
982
+ name: 'NOTIFICATION_SERVICE',
983
+ transport: Transport.RMQ,
984
+ options: {
985
+ urls: [process.env.RABBITMQ_URL],
986
+ queue: 'notifications_queue',
987
+ queueOptions: { durable: false },
988
+ },
989
+ },
990
+ ]),
991
+ ],
992
+ })
993
+ export class AppModule {}
994
+
995
+
996
+ // src/notifications/notifications.service.ts
997
+ import { Injectable, Inject } from '@nestjs/common';
998
+ import { ClientProxy } from '@nestjs/microservices';
999
+
1000
+ @Injectable()
1001
+ export class NotificationsService {
1002
+ constructor(
1003
+ @Inject('NOTIFICATION_SERVICE') private client: ClientProxy,
1004
+ ) {}
1005
+
1006
+ async sendWelcomeEmail(userId: string, email: string) {
1007
+ return this.client.emit('user_welcome', { userId, email });
1008
+ }
60
1009
  }
61
1010
  ```
62
1011
 
63
1012
  ## Best Practices
64
- - Use modules for organization
65
- - Use DTOs for validation
66
- - Use dependency injection
67
- - Use guards for auth
1013
+
1014
+ ### Do's
1015
+
1016
+ - Use modules to organize features
1017
+ - Use dependency injection throughout
1018
+ - Use DTOs for validation and transformation
1019
+ - Use interceptors for cross-cutting concerns
1020
+ - Use guards for authentication/authorization
1021
+ - Use custom decorators for cleaner code
1022
+ - Write unit and e2e tests
1023
+ - Use class-transformer for responses
1024
+ - Use environment configuration
1025
+ - Document APIs with Swagger
1026
+
1027
+ ### Don'ts
1028
+
1029
+ - Don't put business logic in controllers
1030
+ - Don't skip input validation
1031
+ - Don't expose internal errors
1032
+ - Don't use any type unnecessarily
1033
+ - Don't skip authentication guards
1034
+ - Don't ignore database transactions
1035
+ - Don't hardcode configuration
1036
+ - Don't forget error handling
1037
+ - Don't skip testing
1038
+ - Don't use circular dependencies
1039
+
1040
+ ## References
1041
+
1042
+ - [NestJS Documentation](https://docs.nestjs.com/)
1043
+ - [NestJS Best Practices](https://github.com/nestjs/awesome-nestjs)
1044
+ - [TypeORM Documentation](https://typeorm.io/)
1045
+ - [Passport.js Documentation](http://www.passportjs.org/)
1046
+ - [class-validator](https://github.com/typestack/class-validator)