omgkit 2.2.0 → 2.3.1

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 (60) hide show
  1. package/README.md +3 -3
  2. package/package.json +1 -1
  3. package/plugin/skills/databases/database-management/SKILL.md +288 -0
  4. package/plugin/skills/databases/database-migration/SKILL.md +285 -0
  5. package/plugin/skills/databases/database-schema-design/SKILL.md +195 -0
  6. package/plugin/skills/databases/mongodb/SKILL.md +60 -776
  7. package/plugin/skills/databases/prisma/SKILL.md +53 -744
  8. package/plugin/skills/databases/redis/SKILL.md +53 -860
  9. package/plugin/skills/databases/supabase/SKILL.md +283 -0
  10. package/plugin/skills/devops/aws/SKILL.md +68 -672
  11. package/plugin/skills/devops/github-actions/SKILL.md +54 -657
  12. package/plugin/skills/devops/kubernetes/SKILL.md +67 -602
  13. package/plugin/skills/devops/performance-profiling/SKILL.md +59 -863
  14. package/plugin/skills/frameworks/django/SKILL.md +87 -853
  15. package/plugin/skills/frameworks/express/SKILL.md +95 -1301
  16. package/plugin/skills/frameworks/fastapi/SKILL.md +90 -1198
  17. package/plugin/skills/frameworks/laravel/SKILL.md +87 -1187
  18. package/plugin/skills/frameworks/nestjs/SKILL.md +106 -973
  19. package/plugin/skills/frameworks/react/SKILL.md +94 -962
  20. package/plugin/skills/frameworks/vue/SKILL.md +95 -1242
  21. package/plugin/skills/frontend/accessibility/SKILL.md +91 -1056
  22. package/plugin/skills/frontend/frontend-design/SKILL.md +69 -1262
  23. package/plugin/skills/frontend/responsive/SKILL.md +76 -799
  24. package/plugin/skills/frontend/shadcn-ui/SKILL.md +73 -921
  25. package/plugin/skills/frontend/tailwindcss/SKILL.md +60 -788
  26. package/plugin/skills/frontend/threejs/SKILL.md +72 -1266
  27. package/plugin/skills/languages/javascript/SKILL.md +106 -849
  28. package/plugin/skills/methodology/brainstorming/SKILL.md +70 -576
  29. package/plugin/skills/methodology/defense-in-depth/SKILL.md +79 -831
  30. package/plugin/skills/methodology/dispatching-parallel-agents/SKILL.md +81 -654
  31. package/plugin/skills/methodology/executing-plans/SKILL.md +86 -529
  32. package/plugin/skills/methodology/finishing-development-branch/SKILL.md +95 -586
  33. package/plugin/skills/methodology/problem-solving/SKILL.md +67 -681
  34. package/plugin/skills/methodology/receiving-code-review/SKILL.md +70 -533
  35. package/plugin/skills/methodology/requesting-code-review/SKILL.md +70 -610
  36. package/plugin/skills/methodology/root-cause-tracing/SKILL.md +70 -646
  37. package/plugin/skills/methodology/sequential-thinking/SKILL.md +70 -478
  38. package/plugin/skills/methodology/systematic-debugging/SKILL.md +66 -559
  39. package/plugin/skills/methodology/test-driven-development/SKILL.md +91 -752
  40. package/plugin/skills/methodology/testing-anti-patterns/SKILL.md +78 -687
  41. package/plugin/skills/methodology/token-optimization/SKILL.md +72 -602
  42. package/plugin/skills/methodology/verification-before-completion/SKILL.md +108 -529
  43. package/plugin/skills/methodology/writing-plans/SKILL.md +79 -566
  44. package/plugin/skills/omega/omega-architecture/SKILL.md +91 -752
  45. package/plugin/skills/omega/omega-coding/SKILL.md +161 -552
  46. package/plugin/skills/omega/omega-sprint/SKILL.md +132 -777
  47. package/plugin/skills/omega/omega-testing/SKILL.md +157 -845
  48. package/plugin/skills/omega/omega-thinking/SKILL.md +165 -606
  49. package/plugin/skills/security/better-auth/SKILL.md +46 -1034
  50. package/plugin/skills/security/oauth/SKILL.md +80 -934
  51. package/plugin/skills/security/owasp/SKILL.md +78 -862
  52. package/plugin/skills/testing/playwright/SKILL.md +77 -700
  53. package/plugin/skills/testing/pytest/SKILL.md +73 -811
  54. package/plugin/skills/testing/vitest/SKILL.md +60 -920
  55. package/plugin/skills/tools/document-processing/SKILL.md +111 -838
  56. package/plugin/skills/tools/image-processing/SKILL.md +126 -659
  57. package/plugin/skills/tools/mcp-development/SKILL.md +85 -758
  58. package/plugin/skills/tools/media-processing/SKILL.md +118 -735
  59. package/plugin/stdrules/SKILL_STANDARDS.md +490 -0
  60. package/plugin/skills/SKILL_STANDARDS.md +0 -743
@@ -1,1046 +1,179 @@
1
1
  ---
2
- name: nestjs
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
2
+ name: building-nestjs-apis
3
+ description: Builds enterprise NestJS applications with TypeScript, dependency injection, TypeORM, and microservices patterns. Use when creating scalable Node.js backends, REST/GraphQL APIs, or microservices.
14
4
  ---
15
5
 
16
6
  # NestJS
17
7
 
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
35
-
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
-
52
- @Module({
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])],
106
- controllers: [UsersController],
107
- providers: [UsersService, UsersRepository],
108
- exports: [UsersService],
109
- })
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
8
+ ## Quick Start
137
9
 
138
10
  ```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;
11
+ import { Controller, Get, Module, NestFactory } from '@nestjs/common';
185
12
 
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);
13
+ @Controller('health')
14
+ class HealthController {
15
+ @Get()
16
+ check() {
17
+ return { status: 'ok' };
210
18
  }
211
19
  }
212
20
 
21
+ @Module({ controllers: [HealthController] })
22
+ class AppModule {}
213
23
 
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
- }
24
+ async function bootstrap() {
25
+ const app = await NestFactory.create(AppModule);
26
+ await app.listen(3000);
271
27
  }
28
+ bootstrap();
272
29
  ```
273
30
 
274
- ### 3. DTOs and Validation
275
-
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;
31
+ ## Features
409
32
 
410
- @ApiProperty()
411
- hasMore: boolean;
33
+ | Feature | Description | Guide |
34
+ |---------|-------------|-------|
35
+ | Modules | Dependency injection, providers | [MODULES.md](MODULES.md) |
36
+ | Controllers | Routes, validation, guards | [CONTROLLERS.md](CONTROLLERS.md) |
37
+ | Services | Business logic, repositories | [SERVICES.md](SERVICES.md) |
38
+ | Database | TypeORM, Prisma integration | [DATABASE.md](DATABASE.md) |
39
+ | Auth | Passport, JWT, guards | [AUTH.md](AUTH.md) |
40
+ | Testing | Unit, e2e with Jest | [TESTING.md](TESTING.md) |
412
41
 
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
- ```
42
+ ## Common Patterns
431
43
 
432
- ### 4. Controllers
44
+ ### Controller with Validation
433
45
 
434
46
  ```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')
468
47
  @Controller('users')
469
- @UseGuards(JwtAuthGuard, RolesGuard)
470
- @ApiBearerAuth()
48
+ @UseGuards(JwtAuthGuard)
471
49
  export class UsersController {
472
50
  constructor(private readonly usersService: UsersService) {}
473
51
 
474
52
  @Get()
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);
53
+ @Roles('admin')
54
+ findAll(@Query() query: PaginationDto) {
55
+ return this.usersService.findAll(query);
501
56
  }
502
57
 
503
58
  @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> {
59
+ findOne(@Param('id', ParseUUIDPipe) id: string) {
510
60
  return this.usersService.findOne(id);
511
61
  }
512
62
 
513
63
  @Post()
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);
64
+ create(@Body() dto: CreateUserDto) {
65
+ return this.usersService.create(dto);
539
66
  }
540
67
  }
541
68
  ```
542
69
 
543
- ### 5. Services
70
+ ### Service with Repository
544
71
 
545
72
  ```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
-
566
73
  @Injectable()
567
74
  export class UsersService {
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);
617
-
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 } });
75
+ constructor(private readonly usersRepo: UsersRepository) {}
625
76
 
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,
77
+ async findAll(query: PaginationDto) {
78
+ const [users, total] = await this.usersRepo.findAllWithCount({
79
+ skip: query.skip,
80
+ take: query.limit,
642
81
  });
82
+ return { data: users, total, page: query.page };
643
83
  }
644
84
 
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);
85
+ async findOne(id: string) {
86
+ const user = await this.usersRepo.findOne({ where: { id } });
87
+ if (!user) throw new NotFoundException('User not found');
88
+ return user;
653
89
  }
654
90
 
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;
91
+ async create(dto: CreateUserDto) {
92
+ const exists = await this.usersRepo.findByEmail(dto.email);
93
+ if (exists) throw new ConflictException('Email in use');
94
+ return this.usersRepo.save(this.usersRepo.create(dto));
664
95
  }
665
96
  }
666
97
  ```
667
98
 
668
- ### 6. Authentication
99
+ ### DTO with Validation
669
100
 
670
101
  ```typescript
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;
102
+ export class CreateUserDto {
103
+ @IsEmail()
711
104
  email: string;
712
- role: string;
713
- }
714
-
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
105
 
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
- );
106
+ @IsString()
107
+ @MinLength(2)
108
+ name: string;
755
109
 
756
- if (!requiredRoles) {
757
- return true;
758
- }
110
+ @IsString()
111
+ @MinLength(8)
112
+ @Matches(/^(?=.*[A-Z])(?=.*\d)/, {
113
+ message: 'Password must contain uppercase and number',
114
+ })
115
+ password: string;
759
116
 
760
- const { user } = context.switchToHttp().getRequest();
761
- return requiredRoles.includes(user.role);
762
- }
117
+ @IsOptional()
118
+ @IsEnum(UserRole)
119
+ role?: UserRole;
763
120
  }
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
121
  ```
784
122
 
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();
123
+ ## Workflows
906
124
 
907
- controller = module.get<UsersController>(UsersController);
908
- service = module.get(UsersService);
909
- });
125
+ ### API Development
910
126
 
911
- it('should be defined', () => {
912
- expect(controller).toBeDefined();
913
- });
914
- });
127
+ 1. Create module with `nest g module [name]`
128
+ 2. Create controller and service
129
+ 3. Define DTOs with class-validator
130
+ 4. Add guards for auth/roles
131
+ 5. Write unit and e2e tests
915
132
 
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
133
+ ### Module Structure
972
134
 
973
135
  ```typescript
974
- // src/app.module.ts (Microservice)
975
- import { Module } from '@nestjs/common';
976
- import { ClientsModule, Transport } from '@nestjs/microservices';
977
-
978
136
  @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
- ],
137
+ imports: [TypeOrmModule.forFeature([User])],
138
+ controllers: [UsersController],
139
+ providers: [UsersService, UsersRepository],
140
+ exports: [UsersService],
992
141
  })
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
- }
1009
- }
142
+ export class UsersModule {}
1010
143
  ```
1011
144
 
1012
145
  ## Best Practices
1013
146
 
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
147
+ | Do | Avoid |
148
+ |----|-------|
149
+ | Use dependency injection | Direct instantiation |
150
+ | Validate with DTOs | Trusting input |
151
+ | Use guards for auth | Auth logic in controllers |
152
+ | Use interceptors for cross-cutting | Duplicating logging/transform |
153
+ | Write unit + e2e tests | Skipping test coverage |
1026
154
 
1027
- ### Don'ts
155
+ ## Project Structure
1028
156
 
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
157
+ ```
158
+ src/
159
+ ├── main.ts
160
+ ├── app.module.ts
161
+ ├── common/
162
+ │ ├── decorators/
163
+ │ ├── guards/
164
+ │ ├── interceptors/
165
+ │ └── pipes/
166
+ ├── config/
167
+ ├── users/
168
+ │ ├── users.module.ts
169
+ │ ├── users.controller.ts
170
+ │ ├── users.service.ts
171
+ │ ├── dto/
172
+ │ └── entities/
173
+ └── auth/
174
+ ├── auth.module.ts
175
+ ├── strategies/
176
+ └── guards/
177
+ ```
1041
178
 
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)
179
+ For detailed examples and patterns, see reference files above.