@qazuor/claude-code-config 0.5.0 → 0.6.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.
- package/README.md +106 -41
- package/dist/bin.cjs +963 -84
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +963 -84
- package/dist/bin.js.map +1 -1
- package/dist/index.cjs +73 -56
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +73 -56
- package/dist/index.js.map +1 -1
- package/package.json +23 -24
- package/templates/CLAUDE.md.template +60 -5
- package/templates/agents/README.md +58 -39
- package/templates/agents/_registry.json +43 -202
- package/templates/agents/engineering/{hono-engineer.md → api-engineer.md} +61 -70
- package/templates/agents/engineering/database-engineer.md +253 -0
- package/templates/agents/engineering/frontend-engineer.md +302 -0
- package/templates/hooks/on-notification.sh +0 -0
- package/templates/scripts/add-changelogs.sh +0 -0
- package/templates/scripts/generate-code-registry.ts +0 -0
- package/templates/scripts/health-check.sh +0 -0
- package/templates/scripts/sync-registry.sh +0 -0
- package/templates/scripts/telemetry-report.ts +0 -0
- package/templates/scripts/validate-docs.sh +0 -0
- package/templates/scripts/validate-registry.sh +0 -0
- package/templates/scripts/validate-structure.sh +0 -0
- package/templates/scripts/worktree-cleanup.sh +0 -0
- package/templates/scripts/worktree-create.sh +0 -0
- package/templates/skills/README.md +99 -90
- package/templates/skills/_registry.json +323 -16
- package/templates/skills/api-frameworks/express-patterns.md +411 -0
- package/templates/skills/api-frameworks/fastify-patterns.md +419 -0
- package/templates/skills/api-frameworks/hono-patterns.md +388 -0
- package/templates/skills/api-frameworks/nestjs-patterns.md +497 -0
- package/templates/skills/database/drizzle-patterns.md +449 -0
- package/templates/skills/database/mongoose-patterns.md +503 -0
- package/templates/skills/database/prisma-patterns.md +487 -0
- package/templates/skills/frontend-frameworks/astro-patterns.md +415 -0
- package/templates/skills/frontend-frameworks/nextjs-patterns.md +470 -0
- package/templates/skills/frontend-frameworks/react-patterns.md +516 -0
- package/templates/skills/frontend-frameworks/tanstack-start-patterns.md +469 -0
- package/templates/skills/patterns/atdd-methodology.md +364 -0
- package/templates/skills/patterns/bdd-methodology.md +281 -0
- package/templates/skills/patterns/clean-architecture.md +444 -0
- package/templates/skills/patterns/hexagonal-architecture.md +567 -0
- package/templates/skills/patterns/vertical-slice-architecture.md +502 -0
- package/templates/agents/engineering/astro-engineer.md +0 -293
- package/templates/agents/engineering/db-drizzle-engineer.md +0 -360
- package/templates/agents/engineering/express-engineer.md +0 -316
- package/templates/agents/engineering/fastify-engineer.md +0 -399
- package/templates/agents/engineering/mongoose-engineer.md +0 -473
- package/templates/agents/engineering/nestjs-engineer.md +0 -429
- package/templates/agents/engineering/nextjs-engineer.md +0 -451
- package/templates/agents/engineering/prisma-engineer.md +0 -432
- package/templates/agents/engineering/react-senior-dev.md +0 -394
- package/templates/agents/engineering/tanstack-start-engineer.md +0 -447
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
# NestJS Framework Patterns
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
NestJS is a progressive Node.js framework for building efficient, scalable server-side applications. This skill provides patterns for implementing APIs with NestJS.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Module Structure
|
|
10
|
+
|
|
11
|
+
**Pattern**: Feature-based modules with clear boundaries
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
// items/items.module.ts
|
|
15
|
+
import { Module } from '@nestjs/common';
|
|
16
|
+
import { TypeOrmModule } from '@nestjs/typeorm';
|
|
17
|
+
import { ItemsController } from './items.controller';
|
|
18
|
+
import { ItemsService } from './items.service';
|
|
19
|
+
import { Item } from './entities/item.entity';
|
|
20
|
+
|
|
21
|
+
@Module({
|
|
22
|
+
imports: [TypeOrmModule.forFeature([Item])],
|
|
23
|
+
controllers: [ItemsController],
|
|
24
|
+
providers: [ItemsService],
|
|
25
|
+
exports: [ItemsService],
|
|
26
|
+
})
|
|
27
|
+
export class ItemsModule {}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### App Module
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
// app.module.ts
|
|
34
|
+
import { Module } from '@nestjs/common';
|
|
35
|
+
import { ConfigModule } from '@nestjs/config';
|
|
36
|
+
import { TypeOrmModule } from '@nestjs/typeorm';
|
|
37
|
+
import { ItemsModule } from './items/items.module';
|
|
38
|
+
import { UsersModule } from './users/users.module';
|
|
39
|
+
import { AuthModule } from './auth/auth.module';
|
|
40
|
+
|
|
41
|
+
@Module({
|
|
42
|
+
imports: [
|
|
43
|
+
ConfigModule.forRoot({ isGlobal: true }),
|
|
44
|
+
TypeOrmModule.forRoot({
|
|
45
|
+
type: 'postgres',
|
|
46
|
+
url: process.env.DATABASE_URL,
|
|
47
|
+
autoLoadEntities: true,
|
|
48
|
+
synchronize: process.env.NODE_ENV === 'development',
|
|
49
|
+
}),
|
|
50
|
+
AuthModule,
|
|
51
|
+
ItemsModule,
|
|
52
|
+
UsersModule,
|
|
53
|
+
],
|
|
54
|
+
})
|
|
55
|
+
export class AppModule {}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Controller Pattern
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
// items/items.controller.ts
|
|
64
|
+
import {
|
|
65
|
+
Controller,
|
|
66
|
+
Get,
|
|
67
|
+
Post,
|
|
68
|
+
Put,
|
|
69
|
+
Delete,
|
|
70
|
+
Body,
|
|
71
|
+
Param,
|
|
72
|
+
Query,
|
|
73
|
+
UseGuards,
|
|
74
|
+
HttpCode,
|
|
75
|
+
HttpStatus,
|
|
76
|
+
ParseUUIDPipe,
|
|
77
|
+
} from '@nestjs/common';
|
|
78
|
+
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
|
|
79
|
+
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
|
|
80
|
+
import { CurrentUser } from '../auth/decorators/current-user.decorator';
|
|
81
|
+
import { ItemsService } from './items.service';
|
|
82
|
+
import { CreateItemDto } from './dto/create-item.dto';
|
|
83
|
+
import { UpdateItemDto } from './dto/update-item.dto';
|
|
84
|
+
import { QueryItemDto } from './dto/query-item.dto';
|
|
85
|
+
|
|
86
|
+
@ApiTags('items')
|
|
87
|
+
@Controller('items')
|
|
88
|
+
export class ItemsController {
|
|
89
|
+
constructor(private readonly itemsService: ItemsService) {}
|
|
90
|
+
|
|
91
|
+
@Get()
|
|
92
|
+
@ApiOperation({ summary: 'Get all items' })
|
|
93
|
+
async findAll(@Query() query: QueryItemDto) {
|
|
94
|
+
return this.itemsService.findAll(query);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
@Get(':id')
|
|
98
|
+
@ApiOperation({ summary: 'Get item by ID' })
|
|
99
|
+
async findOne(@Param('id', ParseUUIDPipe) id: string) {
|
|
100
|
+
return this.itemsService.findOne(id);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@Post()
|
|
104
|
+
@UseGuards(JwtAuthGuard)
|
|
105
|
+
@ApiBearerAuth()
|
|
106
|
+
@ApiOperation({ summary: 'Create new item' })
|
|
107
|
+
async create(
|
|
108
|
+
@Body() createItemDto: CreateItemDto,
|
|
109
|
+
@CurrentUser() user: User,
|
|
110
|
+
) {
|
|
111
|
+
return this.itemsService.create(createItemDto, user);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
@Put(':id')
|
|
115
|
+
@UseGuards(JwtAuthGuard)
|
|
116
|
+
@ApiBearerAuth()
|
|
117
|
+
@ApiOperation({ summary: 'Update item' })
|
|
118
|
+
async update(
|
|
119
|
+
@Param('id', ParseUUIDPipe) id: string,
|
|
120
|
+
@Body() updateItemDto: UpdateItemDto,
|
|
121
|
+
@CurrentUser() user: User,
|
|
122
|
+
) {
|
|
123
|
+
return this.itemsService.update(id, updateItemDto, user);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
@Delete(':id')
|
|
127
|
+
@UseGuards(JwtAuthGuard)
|
|
128
|
+
@ApiBearerAuth()
|
|
129
|
+
@HttpCode(HttpStatus.NO_CONTENT)
|
|
130
|
+
@ApiOperation({ summary: 'Delete item' })
|
|
131
|
+
async remove(
|
|
132
|
+
@Param('id', ParseUUIDPipe) id: string,
|
|
133
|
+
@CurrentUser() user: User,
|
|
134
|
+
) {
|
|
135
|
+
return this.itemsService.remove(id, user);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Service Pattern
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
// items/items.service.ts
|
|
146
|
+
import { Injectable, NotFoundException, ForbiddenException } from '@nestjs/common';
|
|
147
|
+
import { InjectRepository } from '@nestjs/typeorm';
|
|
148
|
+
import { Repository } from 'typeorm';
|
|
149
|
+
import { Item } from './entities/item.entity';
|
|
150
|
+
import { CreateItemDto } from './dto/create-item.dto';
|
|
151
|
+
import { UpdateItemDto } from './dto/update-item.dto';
|
|
152
|
+
import { QueryItemDto } from './dto/query-item.dto';
|
|
153
|
+
|
|
154
|
+
@Injectable()
|
|
155
|
+
export class ItemsService {
|
|
156
|
+
constructor(
|
|
157
|
+
@InjectRepository(Item)
|
|
158
|
+
private readonly itemRepository: Repository<Item>,
|
|
159
|
+
) {}
|
|
160
|
+
|
|
161
|
+
async findAll(query: QueryItemDto) {
|
|
162
|
+
const { page = 1, limit = 10, status } = query;
|
|
163
|
+
|
|
164
|
+
const [items, total] = await this.itemRepository.findAndCount({
|
|
165
|
+
where: status ? { status } : {},
|
|
166
|
+
take: limit,
|
|
167
|
+
skip: (page - 1) * limit,
|
|
168
|
+
order: { createdAt: 'DESC' },
|
|
169
|
+
relations: ['author'],
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
data: items,
|
|
174
|
+
pagination: {
|
|
175
|
+
total,
|
|
176
|
+
page,
|
|
177
|
+
limit,
|
|
178
|
+
totalPages: Math.ceil(total / limit),
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async findOne(id: string) {
|
|
184
|
+
const item = await this.itemRepository.findOne({
|
|
185
|
+
where: { id },
|
|
186
|
+
relations: ['author'],
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
if (!item) {
|
|
190
|
+
throw new NotFoundException(`Item #${id} not found`);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return item;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async create(createItemDto: CreateItemDto, user: User) {
|
|
197
|
+
const item = this.itemRepository.create({
|
|
198
|
+
...createItemDto,
|
|
199
|
+
authorId: user.id,
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
return this.itemRepository.save(item);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async update(id: string, updateItemDto: UpdateItemDto, user: User) {
|
|
206
|
+
const item = await this.findOne(id);
|
|
207
|
+
|
|
208
|
+
if (item.authorId !== user.id) {
|
|
209
|
+
throw new ForbiddenException('You can only update your own items');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
Object.assign(item, updateItemDto);
|
|
213
|
+
return this.itemRepository.save(item);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async remove(id: string, user: User) {
|
|
217
|
+
const item = await this.findOne(id);
|
|
218
|
+
|
|
219
|
+
if (item.authorId !== user.id) {
|
|
220
|
+
throw new ForbiddenException('You can only delete your own items');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
await this.itemRepository.remove(item);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## DTOs with Validation
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
// items/dto/create-item.dto.ts
|
|
234
|
+
import { IsString, IsNotEmpty, IsOptional, MaxLength } from 'class-validator';
|
|
235
|
+
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
|
|
236
|
+
|
|
237
|
+
export class CreateItemDto {
|
|
238
|
+
@ApiProperty({ description: 'Item title', maxLength: 255 })
|
|
239
|
+
@IsString()
|
|
240
|
+
@IsNotEmpty()
|
|
241
|
+
@MaxLength(255)
|
|
242
|
+
title: string;
|
|
243
|
+
|
|
244
|
+
@ApiPropertyOptional({ description: 'Item description' })
|
|
245
|
+
@IsString()
|
|
246
|
+
@IsOptional()
|
|
247
|
+
description?: string;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// items/dto/update-item.dto.ts
|
|
251
|
+
import { PartialType } from '@nestjs/swagger';
|
|
252
|
+
import { CreateItemDto } from './create-item.dto';
|
|
253
|
+
|
|
254
|
+
export class UpdateItemDto extends PartialType(CreateItemDto) {}
|
|
255
|
+
|
|
256
|
+
// items/dto/query-item.dto.ts
|
|
257
|
+
import { IsOptional, IsInt, Min, IsEnum } from 'class-validator';
|
|
258
|
+
import { Type } from 'class-transformer';
|
|
259
|
+
import { ApiPropertyOptional } from '@nestjs/swagger';
|
|
260
|
+
|
|
261
|
+
export class QueryItemDto {
|
|
262
|
+
@ApiPropertyOptional({ default: 1 })
|
|
263
|
+
@IsOptional()
|
|
264
|
+
@Type(() => Number)
|
|
265
|
+
@IsInt()
|
|
266
|
+
@Min(1)
|
|
267
|
+
page?: number = 1;
|
|
268
|
+
|
|
269
|
+
@ApiPropertyOptional({ default: 10 })
|
|
270
|
+
@IsOptional()
|
|
271
|
+
@Type(() => Number)
|
|
272
|
+
@IsInt()
|
|
273
|
+
@Min(1)
|
|
274
|
+
limit?: number = 10;
|
|
275
|
+
|
|
276
|
+
@ApiPropertyOptional({ enum: ['active', 'archived'] })
|
|
277
|
+
@IsOptional()
|
|
278
|
+
@IsEnum(['active', 'archived'])
|
|
279
|
+
status?: string;
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## Guards and Decorators
|
|
286
|
+
|
|
287
|
+
### JWT Auth Guard
|
|
288
|
+
|
|
289
|
+
```typescript
|
|
290
|
+
// auth/guards/jwt-auth.guard.ts
|
|
291
|
+
import { Injectable, ExecutionContext } from '@nestjs/common';
|
|
292
|
+
import { AuthGuard } from '@nestjs/passport';
|
|
293
|
+
|
|
294
|
+
@Injectable()
|
|
295
|
+
export class JwtAuthGuard extends AuthGuard('jwt') {
|
|
296
|
+
canActivate(context: ExecutionContext) {
|
|
297
|
+
return super.canActivate(context);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
### Current User Decorator
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
// auth/decorators/current-user.decorator.ts
|
|
306
|
+
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
|
|
307
|
+
|
|
308
|
+
export const CurrentUser = createParamDecorator(
|
|
309
|
+
(data: unknown, ctx: ExecutionContext) => {
|
|
310
|
+
const request = ctx.switchToHttp().getRequest();
|
|
311
|
+
return request.user;
|
|
312
|
+
},
|
|
313
|
+
);
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### Roles Guard
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
// auth/guards/roles.guard.ts
|
|
320
|
+
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
|
|
321
|
+
import { Reflector } from '@nestjs/core';
|
|
322
|
+
import { ROLES_KEY } from '../decorators/roles.decorator';
|
|
323
|
+
|
|
324
|
+
@Injectable()
|
|
325
|
+
export class RolesGuard implements CanActivate {
|
|
326
|
+
constructor(private reflector: Reflector) {}
|
|
327
|
+
|
|
328
|
+
canActivate(context: ExecutionContext): boolean {
|
|
329
|
+
const requiredRoles = this.reflector.getAllAndOverride<string[]>(ROLES_KEY, [
|
|
330
|
+
context.getHandler(),
|
|
331
|
+
context.getClass(),
|
|
332
|
+
]);
|
|
333
|
+
|
|
334
|
+
if (!requiredRoles) {
|
|
335
|
+
return true;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const { user } = context.switchToHttp().getRequest();
|
|
339
|
+
return requiredRoles.some((role) => user.roles?.includes(role));
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
## Exception Filters
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
// common/filters/http-exception.filter.ts
|
|
350
|
+
import {
|
|
351
|
+
ExceptionFilter,
|
|
352
|
+
Catch,
|
|
353
|
+
ArgumentsHost,
|
|
354
|
+
HttpException,
|
|
355
|
+
HttpStatus,
|
|
356
|
+
} from '@nestjs/common';
|
|
357
|
+
import { Response } from 'express';
|
|
358
|
+
|
|
359
|
+
@Catch()
|
|
360
|
+
export class AllExceptionsFilter implements ExceptionFilter {
|
|
361
|
+
catch(exception: unknown, host: ArgumentsHost) {
|
|
362
|
+
const ctx = host.switchToHttp();
|
|
363
|
+
const response = ctx.getResponse<Response>();
|
|
364
|
+
|
|
365
|
+
const status =
|
|
366
|
+
exception instanceof HttpException
|
|
367
|
+
? exception.getStatus()
|
|
368
|
+
: HttpStatus.INTERNAL_SERVER_ERROR;
|
|
369
|
+
|
|
370
|
+
const message =
|
|
371
|
+
exception instanceof HttpException
|
|
372
|
+
? exception.getResponse()
|
|
373
|
+
: 'Internal server error';
|
|
374
|
+
|
|
375
|
+
response.status(status).json({
|
|
376
|
+
success: false,
|
|
377
|
+
statusCode: status,
|
|
378
|
+
error: typeof message === 'string' ? { message } : message,
|
|
379
|
+
timestamp: new Date().toISOString(),
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
---
|
|
386
|
+
|
|
387
|
+
## Testing
|
|
388
|
+
|
|
389
|
+
```typescript
|
|
390
|
+
// items/items.controller.spec.ts
|
|
391
|
+
import { Test, TestingModule } from '@nestjs/testing';
|
|
392
|
+
import { ItemsController } from './items.controller';
|
|
393
|
+
import { ItemsService } from './items.service';
|
|
394
|
+
|
|
395
|
+
describe('ItemsController', () => {
|
|
396
|
+
let controller: ItemsController;
|
|
397
|
+
let service: ItemsService;
|
|
398
|
+
|
|
399
|
+
const mockItemsService = {
|
|
400
|
+
findAll: jest.fn(),
|
|
401
|
+
findOne: jest.fn(),
|
|
402
|
+
create: jest.fn(),
|
|
403
|
+
update: jest.fn(),
|
|
404
|
+
remove: jest.fn(),
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
beforeEach(async () => {
|
|
408
|
+
const module: TestingModule = await Test.createTestingModule({
|
|
409
|
+
controllers: [ItemsController],
|
|
410
|
+
providers: [
|
|
411
|
+
{
|
|
412
|
+
provide: ItemsService,
|
|
413
|
+
useValue: mockItemsService,
|
|
414
|
+
},
|
|
415
|
+
],
|
|
416
|
+
}).compile();
|
|
417
|
+
|
|
418
|
+
controller = module.get<ItemsController>(ItemsController);
|
|
419
|
+
service = module.get<ItemsService>(ItemsService);
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
it('should be defined', () => {
|
|
423
|
+
expect(controller).toBeDefined();
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
describe('findAll', () => {
|
|
427
|
+
it('should return array of items', async () => {
|
|
428
|
+
const result = { data: [], pagination: {} };
|
|
429
|
+
mockItemsService.findAll.mockResolvedValue(result);
|
|
430
|
+
|
|
431
|
+
expect(await controller.findAll({})).toBe(result);
|
|
432
|
+
});
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
describe('create', () => {
|
|
436
|
+
it('should create item', async () => {
|
|
437
|
+
const dto = { title: 'Test' };
|
|
438
|
+
const user = { id: '1' };
|
|
439
|
+
const item = { id: '1', ...dto };
|
|
440
|
+
|
|
441
|
+
mockItemsService.create.mockResolvedValue(item);
|
|
442
|
+
|
|
443
|
+
expect(await controller.create(dto, user as any)).toBe(item);
|
|
444
|
+
expect(mockItemsService.create).toHaveBeenCalledWith(dto, user);
|
|
445
|
+
});
|
|
446
|
+
});
|
|
447
|
+
});
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
---
|
|
451
|
+
|
|
452
|
+
## Project Structure
|
|
453
|
+
|
|
454
|
+
```
|
|
455
|
+
{API_PATH}/
|
|
456
|
+
├── src/
|
|
457
|
+
│ ├── main.ts
|
|
458
|
+
│ ├── app.module.ts
|
|
459
|
+
│ ├── common/
|
|
460
|
+
│ │ ├── filters/
|
|
461
|
+
│ │ ├── guards/
|
|
462
|
+
│ │ ├── interceptors/
|
|
463
|
+
│ │ └── pipes/
|
|
464
|
+
│ ├── auth/
|
|
465
|
+
│ │ ├── auth.module.ts
|
|
466
|
+
│ │ ├── auth.service.ts
|
|
467
|
+
│ │ ├── guards/
|
|
468
|
+
│ │ └── decorators/
|
|
469
|
+
│ └── items/
|
|
470
|
+
│ ├── items.module.ts
|
|
471
|
+
│ ├── items.controller.ts
|
|
472
|
+
│ ├── items.service.ts
|
|
473
|
+
│ ├── dto/
|
|
474
|
+
│ └── entities/
|
|
475
|
+
└── test/
|
|
476
|
+
└── items.e2e-spec.ts
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
---
|
|
480
|
+
|
|
481
|
+
## Best Practices
|
|
482
|
+
|
|
483
|
+
### Good
|
|
484
|
+
|
|
485
|
+
- Use modules for feature encapsulation
|
|
486
|
+
- Use DTOs for validation with class-validator
|
|
487
|
+
- Use guards for authentication/authorization
|
|
488
|
+
- Use interceptors for response transformation
|
|
489
|
+
- Use dependency injection consistently
|
|
490
|
+
|
|
491
|
+
### Bad
|
|
492
|
+
|
|
493
|
+
- Circular dependencies between modules
|
|
494
|
+
- Business logic in controllers
|
|
495
|
+
- Skipping validation
|
|
496
|
+
- Not using pipes for transformation
|
|
497
|
+
- Hardcoded values instead of ConfigService
|