@malamute/ai-rules 1.0.0 → 1.3.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 (145) hide show
  1. package/README.md +272 -121
  2. package/bin/cli.js +5 -2
  3. package/configs/_shared/CLAUDE.md +52 -149
  4. package/configs/_shared/rules/conventions/documentation.md +324 -0
  5. package/configs/_shared/rules/conventions/git.md +265 -0
  6. package/configs/_shared/rules/conventions/npm.md +80 -0
  7. package/configs/_shared/{.claude/rules → rules/conventions}/performance.md +1 -1
  8. package/configs/_shared/rules/conventions/principles.md +334 -0
  9. package/configs/_shared/rules/devops/ci-cd.md +262 -0
  10. package/configs/_shared/rules/devops/docker.md +275 -0
  11. package/configs/_shared/rules/devops/nx.md +194 -0
  12. package/configs/_shared/rules/domain/backend/api-design.md +203 -0
  13. package/configs/_shared/rules/lang/csharp/async.md +220 -0
  14. package/configs/_shared/rules/lang/csharp/csharp.md +314 -0
  15. package/configs/_shared/rules/lang/csharp/linq.md +210 -0
  16. package/configs/_shared/rules/lang/python/async.md +337 -0
  17. package/configs/_shared/rules/lang/python/celery.md +476 -0
  18. package/configs/_shared/rules/lang/python/config.md +339 -0
  19. package/configs/{python/.claude/rules → _shared/rules/lang/python}/database/sqlalchemy.md +6 -1
  20. package/configs/_shared/rules/lang/python/deployment.md +523 -0
  21. package/configs/_shared/rules/lang/python/error-handling.md +330 -0
  22. package/configs/_shared/rules/lang/python/migrations.md +421 -0
  23. package/configs/_shared/rules/lang/python/python.md +172 -0
  24. package/configs/_shared/rules/lang/python/repository.md +383 -0
  25. package/configs/{python/.claude/rules → _shared/rules/lang/python}/testing.md +2 -69
  26. package/configs/_shared/rules/lang/typescript/async.md +447 -0
  27. package/configs/_shared/rules/lang/typescript/generics.md +356 -0
  28. package/configs/_shared/rules/lang/typescript/typescript.md +212 -0
  29. package/configs/_shared/rules/quality/error-handling.md +48 -0
  30. package/configs/_shared/rules/quality/logging.md +45 -0
  31. package/configs/_shared/rules/quality/observability.md +240 -0
  32. package/configs/_shared/rules/quality/testing-patterns.md +65 -0
  33. package/configs/_shared/rules/security/secrets-management.md +222 -0
  34. package/configs/_shared/skills/analysis/explore/SKILL.md +257 -0
  35. package/configs/_shared/skills/analysis/security-audit/SKILL.md +184 -0
  36. package/configs/_shared/skills/dev/api-endpoint/SKILL.md +126 -0
  37. package/configs/_shared/{.claude/commands/generate-tests.md → skills/dev/generate-tests/SKILL.md} +6 -0
  38. package/configs/_shared/{.claude/commands/fix-issue.md → skills/git/fix-issue/SKILL.md} +6 -0
  39. package/configs/_shared/{.claude/commands/review-pr.md → skills/git/review-pr/SKILL.md} +6 -0
  40. package/configs/_shared/skills/infra/deploy/SKILL.md +139 -0
  41. package/configs/_shared/skills/infra/docker/SKILL.md +95 -0
  42. package/configs/_shared/skills/infra/migration/SKILL.md +158 -0
  43. package/configs/_shared/skills/nx/nx-affected/SKILL.md +72 -0
  44. package/configs/_shared/skills/nx/nx-lib/SKILL.md +375 -0
  45. package/configs/angular/CLAUDE.md +24 -216
  46. package/configs/angular/{.claude/rules → rules/core}/components.md +69 -15
  47. package/configs/angular/rules/core/resource.md +285 -0
  48. package/configs/angular/rules/core/signals.md +323 -0
  49. package/configs/angular/rules/http.md +338 -0
  50. package/configs/angular/rules/routing.md +291 -0
  51. package/configs/angular/rules/ssr.md +312 -0
  52. package/configs/angular/rules/state/signal-store.md +408 -0
  53. package/configs/angular/{.claude/rules → rules/state}/state.md +2 -2
  54. package/configs/angular/{.claude/rules → rules}/testing.md +7 -7
  55. package/configs/angular/rules/ui/aria.md +422 -0
  56. package/configs/angular/rules/ui/forms.md +424 -0
  57. package/configs/angular/rules/ui/pipes-directives.md +335 -0
  58. package/configs/angular/{.claude/settings.json → settings.json} +3 -0
  59. package/configs/dotnet/CLAUDE.md +53 -286
  60. package/configs/dotnet/rules/background-services.md +552 -0
  61. package/configs/dotnet/rules/configuration.md +426 -0
  62. package/configs/dotnet/rules/ddd.md +447 -0
  63. package/configs/dotnet/rules/dependency-injection.md +343 -0
  64. package/configs/dotnet/rules/mediatr.md +320 -0
  65. package/configs/dotnet/rules/middleware.md +489 -0
  66. package/configs/dotnet/rules/result-pattern.md +363 -0
  67. package/configs/dotnet/rules/validation.md +388 -0
  68. package/configs/dotnet/settings.json +29 -0
  69. package/configs/fastapi/CLAUDE.md +144 -0
  70. package/configs/fastapi/rules/background-tasks.md +254 -0
  71. package/configs/fastapi/rules/dependencies.md +170 -0
  72. package/configs/{python/.claude → fastapi}/rules/fastapi.md +61 -1
  73. package/configs/fastapi/rules/lifespan.md +274 -0
  74. package/configs/fastapi/rules/middleware.md +229 -0
  75. package/configs/fastapi/rules/pydantic.md +433 -0
  76. package/configs/fastapi/rules/responses.md +251 -0
  77. package/configs/fastapi/rules/routers.md +202 -0
  78. package/configs/fastapi/rules/security.md +222 -0
  79. package/configs/fastapi/rules/testing.md +251 -0
  80. package/configs/fastapi/rules/websockets.md +298 -0
  81. package/configs/fastapi/settings.json +35 -0
  82. package/configs/flask/CLAUDE.md +166 -0
  83. package/configs/flask/rules/blueprints.md +208 -0
  84. package/configs/flask/rules/cli.md +285 -0
  85. package/configs/flask/rules/configuration.md +281 -0
  86. package/configs/flask/rules/context.md +238 -0
  87. package/configs/flask/rules/error-handlers.md +278 -0
  88. package/configs/flask/rules/extensions.md +278 -0
  89. package/configs/flask/rules/flask.md +171 -0
  90. package/configs/flask/rules/marshmallow.md +206 -0
  91. package/configs/flask/rules/security.md +267 -0
  92. package/configs/flask/rules/testing.md +284 -0
  93. package/configs/flask/settings.json +35 -0
  94. package/configs/nestjs/CLAUDE.md +57 -215
  95. package/configs/nestjs/rules/common-patterns.md +300 -0
  96. package/configs/nestjs/rules/filters.md +376 -0
  97. package/configs/nestjs/rules/interceptors.md +317 -0
  98. package/configs/nestjs/rules/middleware.md +321 -0
  99. package/configs/nestjs/{.claude/rules → rules}/modules.md +26 -0
  100. package/configs/nestjs/rules/pipes.md +351 -0
  101. package/configs/nestjs/rules/websockets.md +451 -0
  102. package/configs/nestjs/settings.json +31 -0
  103. package/configs/nextjs/CLAUDE.md +69 -331
  104. package/configs/nextjs/rules/api-routes.md +358 -0
  105. package/configs/nextjs/rules/authentication.md +355 -0
  106. package/configs/nextjs/{.claude/rules → rules}/components.md +52 -0
  107. package/configs/nextjs/rules/data-fetching.md +249 -0
  108. package/configs/nextjs/rules/database.md +400 -0
  109. package/configs/nextjs/rules/middleware.md +303 -0
  110. package/configs/nextjs/rules/routing.md +324 -0
  111. package/configs/nextjs/rules/seo.md +350 -0
  112. package/configs/nextjs/rules/server-actions.md +353 -0
  113. package/configs/nextjs/{.claude/rules → rules}/state/zustand.md +6 -6
  114. package/configs/nextjs/{.claude/settings.json → settings.json} +7 -0
  115. package/package.json +24 -9
  116. package/src/cli.js +218 -0
  117. package/src/config.js +63 -0
  118. package/src/index.js +4 -0
  119. package/src/installer.js +414 -0
  120. package/src/merge.js +109 -0
  121. package/src/tech-config.json +45 -0
  122. package/src/utils.js +88 -0
  123. package/configs/dotnet/.claude/settings.json +0 -9
  124. package/configs/nestjs/.claude/settings.json +0 -15
  125. package/configs/python/.claude/rules/flask.md +0 -332
  126. package/configs/python/.claude/settings.json +0 -18
  127. package/configs/python/CLAUDE.md +0 -273
  128. package/src/install.js +0 -315
  129. /package/configs/_shared/{.claude/rules → rules/domain/frontend}/accessibility.md +0 -0
  130. /package/configs/_shared/{.claude/rules → rules/security}/security.md +0 -0
  131. /package/configs/_shared/{.claude/skills → skills/dev}/debug/SKILL.md +0 -0
  132. /package/configs/_shared/{.claude/skills → skills/dev}/learning/SKILL.md +0 -0
  133. /package/configs/_shared/{.claude/skills → skills/dev}/spec/SKILL.md +0 -0
  134. /package/configs/_shared/{.claude/skills → skills/git}/review/SKILL.md +0 -0
  135. /package/configs/dotnet/{.claude/rules → rules}/api.md +0 -0
  136. /package/configs/dotnet/{.claude/rules → rules}/architecture.md +0 -0
  137. /package/configs/dotnet/{.claude/rules → rules}/database/efcore.md +0 -0
  138. /package/configs/dotnet/{.claude/rules → rules}/testing.md +0 -0
  139. /package/configs/nestjs/{.claude/rules → rules}/auth.md +0 -0
  140. /package/configs/nestjs/{.claude/rules → rules}/database/prisma.md +0 -0
  141. /package/configs/nestjs/{.claude/rules → rules}/database/typeorm.md +0 -0
  142. /package/configs/nestjs/{.claude/rules → rules}/testing.md +0 -0
  143. /package/configs/nestjs/{.claude/rules → rules}/validation.md +0 -0
  144. /package/configs/nextjs/{.claude/rules → rules}/state/redux-toolkit.md +0 -0
  145. /package/configs/nextjs/{.claude/rules → rules}/testing.md +0 -0
@@ -0,0 +1,321 @@
1
+ ---
2
+ paths:
3
+ - "**/*.middleware.ts"
4
+ - "**/middleware/**/*.ts"
5
+ ---
6
+
7
+ # NestJS Middleware
8
+
9
+ ## Functional Middleware
10
+
11
+ ```typescript
12
+ // middleware/logger.middleware.ts
13
+ import { Request, Response, NextFunction } from 'express';
14
+
15
+ export function loggerMiddleware(req: Request, res: Response, next: NextFunction) {
16
+ const start = Date.now();
17
+
18
+ res.on('finish', () => {
19
+ const duration = Date.now() - start;
20
+ console.log(`${req.method} ${req.url} ${res.statusCode} - ${duration}ms`);
21
+ });
22
+
23
+ next();
24
+ }
25
+
26
+ // Apply in module
27
+ export class AppModule implements NestModule {
28
+ configure(consumer: MiddlewareConsumer) {
29
+ consumer.apply(loggerMiddleware).forRoutes('*');
30
+ }
31
+ }
32
+ ```
33
+
34
+ ## Class Middleware
35
+
36
+ ```typescript
37
+ // middleware/auth.middleware.ts
38
+ import { Injectable, NestMiddleware, UnauthorizedException } from '@nestjs/common';
39
+ import { Request, Response, NextFunction } from 'express';
40
+ import { JwtService } from '@nestjs/jwt';
41
+
42
+ @Injectable()
43
+ export class AuthMiddleware implements NestMiddleware {
44
+ constructor(private readonly jwtService: JwtService) {}
45
+
46
+ async use(req: Request, res: Response, next: NextFunction) {
47
+ const authHeader = req.headers.authorization;
48
+
49
+ if (!authHeader?.startsWith('Bearer ')) {
50
+ throw new UnauthorizedException('Missing token');
51
+ }
52
+
53
+ const token = authHeader.split(' ')[1];
54
+
55
+ try {
56
+ const payload = await this.jwtService.verifyAsync(token);
57
+ req['user'] = payload;
58
+ next();
59
+ } catch {
60
+ throw new UnauthorizedException('Invalid token');
61
+ }
62
+ }
63
+ }
64
+ ```
65
+
66
+ ## Correlation ID Middleware
67
+
68
+ ```typescript
69
+ // middleware/correlation-id.middleware.ts
70
+ import { Injectable, NestMiddleware } from '@nestjs/common';
71
+ import { Request, Response, NextFunction } from 'express';
72
+ import { randomUUID } from 'crypto';
73
+
74
+ @Injectable()
75
+ export class CorrelationIdMiddleware implements NestMiddleware {
76
+ use(req: Request, res: Response, next: NextFunction) {
77
+ const correlationId = req.headers['x-correlation-id'] as string || randomUUID();
78
+
79
+ req['correlationId'] = correlationId;
80
+ res.setHeader('x-correlation-id', correlationId);
81
+
82
+ next();
83
+ }
84
+ }
85
+ ```
86
+
87
+ ## Request Validation Middleware
88
+
89
+ ```typescript
90
+ // middleware/content-type.middleware.ts
91
+ import { Injectable, NestMiddleware, UnsupportedMediaTypeException } from '@nestjs/common';
92
+ import { Request, Response, NextFunction } from 'express';
93
+
94
+ @Injectable()
95
+ export class JsonContentTypeMiddleware implements NestMiddleware {
96
+ use(req: Request, res: Response, next: NextFunction) {
97
+ if (['POST', 'PUT', 'PATCH'].includes(req.method)) {
98
+ const contentType = req.headers['content-type'];
99
+
100
+ if (!contentType?.includes('application/json')) {
101
+ throw new UnsupportedMediaTypeException('Content-Type must be application/json');
102
+ }
103
+ }
104
+
105
+ next();
106
+ }
107
+ }
108
+ ```
109
+
110
+ ## Rate Limiting Middleware
111
+
112
+ ```typescript
113
+ // middleware/rate-limit.middleware.ts
114
+ import { Injectable, NestMiddleware, HttpException, HttpStatus } from '@nestjs/common';
115
+ import { Request, Response, NextFunction } from 'express';
116
+ import { Redis } from 'ioredis';
117
+
118
+ @Injectable()
119
+ export class RateLimitMiddleware implements NestMiddleware {
120
+ private readonly windowMs = 60 * 1000; // 1 minute
121
+ private readonly maxRequests = 100;
122
+
123
+ constructor(private readonly redis: Redis) {}
124
+
125
+ async use(req: Request, res: Response, next: NextFunction) {
126
+ const key = `rate-limit:${req.ip}`;
127
+ const current = await this.redis.incr(key);
128
+
129
+ if (current === 1) {
130
+ await this.redis.pexpire(key, this.windowMs);
131
+ }
132
+
133
+ const remaining = Math.max(0, this.maxRequests - current);
134
+ const ttl = await this.redis.pttl(key);
135
+
136
+ res.setHeader('X-RateLimit-Limit', this.maxRequests);
137
+ res.setHeader('X-RateLimit-Remaining', remaining);
138
+ res.setHeader('X-RateLimit-Reset', Math.ceil(Date.now() / 1000 + ttl / 1000));
139
+
140
+ if (current > this.maxRequests) {
141
+ throw new HttpException('Too Many Requests', HttpStatus.TOO_MANY_REQUESTS);
142
+ }
143
+
144
+ next();
145
+ }
146
+ }
147
+ ```
148
+
149
+ ## Request Sanitization Middleware
150
+
151
+ ```typescript
152
+ // middleware/sanitize.middleware.ts
153
+ import { Injectable, NestMiddleware } from '@nestjs/common';
154
+ import { Request, Response, NextFunction } from 'express';
155
+ import * as sanitizeHtml from 'sanitize-html';
156
+
157
+ @Injectable()
158
+ export class SanitizeMiddleware implements NestMiddleware {
159
+ use(req: Request, res: Response, next: NextFunction) {
160
+ if (req.body) {
161
+ req.body = this.sanitizeObject(req.body);
162
+ }
163
+
164
+ next();
165
+ }
166
+
167
+ private sanitizeObject(obj: Record<string, unknown>): Record<string, unknown> {
168
+ const sanitized: Record<string, unknown> = {};
169
+
170
+ for (const [key, value] of Object.entries(obj)) {
171
+ if (typeof value === 'string') {
172
+ sanitized[key] = sanitizeHtml(value, {
173
+ allowedTags: [],
174
+ allowedAttributes: {},
175
+ });
176
+ } else if (typeof value === 'object' && value !== null) {
177
+ sanitized[key] = this.sanitizeObject(value as Record<string, unknown>);
178
+ } else {
179
+ sanitized[key] = value;
180
+ }
181
+ }
182
+
183
+ return sanitized;
184
+ }
185
+ }
186
+ ```
187
+
188
+ ## Applying Middleware
189
+
190
+ ```typescript
191
+ // app.module.ts
192
+ import { Module, NestModule, MiddlewareConsumer, RequestMethod } from '@nestjs/common';
193
+
194
+ @Module({})
195
+ export class AppModule implements NestModule {
196
+ configure(consumer: MiddlewareConsumer) {
197
+ // Apply to all routes
198
+ consumer.apply(CorrelationIdMiddleware).forRoutes('*');
199
+
200
+ // Apply to specific path
201
+ consumer.apply(AuthMiddleware).forRoutes('api/protected');
202
+
203
+ // Apply to specific controller
204
+ consumer.apply(LoggerMiddleware).forRoutes(UsersController);
205
+
206
+ // Apply with method filter
207
+ consumer
208
+ .apply(JsonContentTypeMiddleware)
209
+ .forRoutes({ path: '*', method: RequestMethod.POST });
210
+
211
+ // Exclude routes
212
+ consumer
213
+ .apply(AuthMiddleware)
214
+ .exclude(
215
+ { path: 'auth/login', method: RequestMethod.POST },
216
+ { path: 'auth/register', method: RequestMethod.POST },
217
+ { path: 'health', method: RequestMethod.GET },
218
+ )
219
+ .forRoutes('*');
220
+
221
+ // Chain multiple middleware
222
+ consumer
223
+ .apply(CorrelationIdMiddleware, LoggerMiddleware, AuthMiddleware)
224
+ .forRoutes('api');
225
+ }
226
+ }
227
+ ```
228
+
229
+ ## Global Middleware (Express)
230
+
231
+ ```typescript
232
+ // main.ts
233
+ import * as helmet from 'helmet';
234
+ import * as compression from 'compression';
235
+ import * as cookieParser from 'cookie-parser';
236
+
237
+ async function bootstrap() {
238
+ const app = await NestFactory.create(AppModule);
239
+
240
+ // Security headers
241
+ app.use(helmet());
242
+
243
+ // Compression
244
+ app.use(compression());
245
+
246
+ // Cookie parsing
247
+ app.use(cookieParser());
248
+
249
+ // CORS
250
+ app.enableCors({
251
+ origin: process.env.ALLOWED_ORIGINS?.split(',') || '*',
252
+ credentials: true,
253
+ });
254
+
255
+ // Body parsing limits
256
+ app.use(express.json({ limit: '10mb' }));
257
+ app.use(express.urlencoded({ extended: true, limit: '10mb' }));
258
+
259
+ await app.listen(3000);
260
+ }
261
+ ```
262
+
263
+ ## Middleware vs Guards vs Interceptors
264
+
265
+ | Feature | Middleware | Guards | Interceptors |
266
+ |---------|------------|--------|--------------|
267
+ | Execution Order | First | After middleware | After guards |
268
+ | Access to ExecutionContext | No | Yes | Yes |
269
+ | Can transform response | No | No | Yes |
270
+ | DI support | Class only | Yes | Yes |
271
+ | Use case | Request processing | Authorization | Transform/logging |
272
+
273
+ ## Anti-patterns
274
+
275
+ ```typescript
276
+ // BAD: Heavy operations in middleware
277
+ @Injectable()
278
+ export class HeavyMiddleware implements NestMiddleware {
279
+ async use(req: Request, res: Response, next: NextFunction) {
280
+ await this.db.query('SELECT * FROM logs'); // Blocks every request!
281
+ next();
282
+ }
283
+ }
284
+
285
+ // GOOD: Keep middleware lightweight
286
+
287
+ // BAD: Not calling next()
288
+ use(req: Request, res: Response, next: NextFunction) {
289
+ if (someCondition) {
290
+ return; // Request hangs!
291
+ }
292
+ next();
293
+ }
294
+
295
+ // GOOD: Always call next() or send response
296
+ use(req: Request, res: Response, next: NextFunction) {
297
+ if (someCondition) {
298
+ res.status(400).json({ error: 'Bad request' });
299
+ return;
300
+ }
301
+ next();
302
+ }
303
+
304
+ // BAD: Modifying response after next()
305
+ async use(req: Request, res: Response, next: NextFunction) {
306
+ next();
307
+ res.setHeader('X-Custom', 'value'); // May not work!
308
+ }
309
+
310
+ // GOOD: Modify before next() or use interceptor
311
+ use(req: Request, res: Response, next: NextFunction) {
312
+ res.setHeader('X-Custom', 'value');
313
+ next();
314
+ }
315
+
316
+ // BAD: Using middleware for auth when guards exist
317
+ // Middleware can't access ExecutionContext
318
+
319
+ // GOOD: Use guards for authorization
320
+ @UseGuards(AuthGuard)
321
+ ```
@@ -3,10 +3,36 @@ paths:
3
3
  - "src/**/*.module.ts"
4
4
  - "src/**/*.controller.ts"
5
5
  - "src/**/*.service.ts"
6
+ - "src/main.ts"
6
7
  ---
7
8
 
8
9
  # NestJS Module Architecture
9
10
 
11
+ ## Global Configuration (main.ts)
12
+
13
+ ### ValidationPipe Setup
14
+
15
+ ```typescript
16
+ import { ValidationPipe } from '@nestjs/common';
17
+
18
+ async function bootstrap() {
19
+ const app = await NestFactory.create(AppModule);
20
+
21
+ app.useGlobalPipes(
22
+ new ValidationPipe({
23
+ whitelist: true, // Strip non-whitelisted properties
24
+ forbidNonWhitelisted: true, // Throw error on extra properties
25
+ transform: true, // Auto-transform payloads to DTO types
26
+ transformOptions: {
27
+ enableImplicitConversion: true,
28
+ },
29
+ }),
30
+ );
31
+
32
+ await app.listen(3000);
33
+ }
34
+ ```
35
+
10
36
  ## Module Design
11
37
 
12
38
  ### Single Responsibility
@@ -0,0 +1,351 @@
1
+ ---
2
+ paths:
3
+ - "**/*.pipe.ts"
4
+ - "**/pipes/**/*.ts"
5
+ ---
6
+
7
+ # NestJS Pipes
8
+
9
+ ## Built-in Pipes
10
+
11
+ ```typescript
12
+ import {
13
+ ValidationPipe,
14
+ ParseIntPipe,
15
+ ParseBoolPipe,
16
+ ParseArrayPipe,
17
+ ParseUUIDPipe,
18
+ ParseEnumPipe,
19
+ DefaultValuePipe,
20
+ } from '@nestjs/common';
21
+
22
+ @Controller('users')
23
+ export class UsersController {
24
+ // ParseIntPipe
25
+ @Get(':id')
26
+ findOne(@Param('id', ParseIntPipe) id: number) {
27
+ return this.usersService.findOne(id);
28
+ }
29
+
30
+ // ParseUUIDPipe with version
31
+ @Get(':uuid')
32
+ findByUuid(@Param('uuid', new ParseUUIDPipe({ version: '4' })) uuid: string) {
33
+ return this.usersService.findByUuid(uuid);
34
+ }
35
+
36
+ // ParseEnumPipe
37
+ @Get()
38
+ findByStatus(
39
+ @Query('status', new ParseEnumPipe(UserStatus)) status: UserStatus,
40
+ ) {
41
+ return this.usersService.findByStatus(status);
42
+ }
43
+
44
+ // DefaultValuePipe
45
+ @Get()
46
+ findAll(
47
+ @Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number,
48
+ @Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit: number,
49
+ ) {
50
+ return this.usersService.findAll({ page, limit });
51
+ }
52
+
53
+ // ParseArrayPipe
54
+ @Get()
55
+ findByIds(
56
+ @Query('ids', new ParseArrayPipe({ items: Number, separator: ',' }))
57
+ ids: number[],
58
+ ) {
59
+ return this.usersService.findByIds(ids);
60
+ }
61
+ }
62
+ ```
63
+
64
+ ## Global Validation Pipe
65
+
66
+ ```typescript
67
+ // main.ts
68
+ import { ValidationPipe } from '@nestjs/common';
69
+
70
+ async function bootstrap() {
71
+ const app = await NestFactory.create(AppModule);
72
+
73
+ app.useGlobalPipes(
74
+ new ValidationPipe({
75
+ whitelist: true, // Strip non-decorated properties
76
+ forbidNonWhitelisted: true, // Throw on extra properties
77
+ transform: true, // Transform payloads to DTO types
78
+ transformOptions: {
79
+ enableImplicitConversion: true,
80
+ },
81
+ exceptionFactory: (errors) => {
82
+ const messages = errors.map((error) => ({
83
+ field: error.property,
84
+ constraints: Object.values(error.constraints || {}),
85
+ }));
86
+ return new BadRequestException({ errors: messages });
87
+ },
88
+ }),
89
+ );
90
+
91
+ await app.listen(3000);
92
+ }
93
+ ```
94
+
95
+ ## Custom Pipes
96
+
97
+ ### Transform Pipe
98
+
99
+ ```typescript
100
+ // pipes/parse-date.pipe.ts
101
+ import { PipeTransform, Injectable, BadRequestException } from '@nestjs/common';
102
+
103
+ @Injectable()
104
+ export class ParseDatePipe implements PipeTransform<string, Date> {
105
+ transform(value: string): Date {
106
+ const date = new Date(value);
107
+
108
+ if (isNaN(date.getTime())) {
109
+ throw new BadRequestException(`Invalid date format: ${value}`);
110
+ }
111
+
112
+ return date;
113
+ }
114
+ }
115
+
116
+ // Usage
117
+ @Get()
118
+ findByDate(@Query('date', ParseDatePipe) date: Date) {
119
+ return this.service.findByDate(date);
120
+ }
121
+ ```
122
+
123
+ ### Validation Pipe with Schema
124
+
125
+ ```typescript
126
+ // pipes/zod-validation.pipe.ts
127
+ import { PipeTransform, BadRequestException } from '@nestjs/common';
128
+ import { ZodSchema, ZodError } from 'zod';
129
+
130
+ export class ZodValidationPipe implements PipeTransform {
131
+ constructor(private readonly schema: ZodSchema) {}
132
+
133
+ transform(value: unknown) {
134
+ try {
135
+ return this.schema.parse(value);
136
+ } catch (error) {
137
+ if (error instanceof ZodError) {
138
+ throw new BadRequestException({
139
+ message: 'Validation failed',
140
+ errors: error.errors.map((e) => ({
141
+ path: e.path.join('.'),
142
+ message: e.message,
143
+ })),
144
+ });
145
+ }
146
+ throw error;
147
+ }
148
+ }
149
+ }
150
+
151
+ // Usage
152
+ const createUserSchema = z.object({
153
+ email: z.string().email(),
154
+ name: z.string().min(2),
155
+ });
156
+
157
+ @Post()
158
+ create(@Body(new ZodValidationPipe(createUserSchema)) dto: CreateUserDto) {
159
+ return this.usersService.create(dto);
160
+ }
161
+ ```
162
+
163
+ ### Async Pipe
164
+
165
+ ```typescript
166
+ // pipes/user-exists.pipe.ts
167
+ import {
168
+ PipeTransform,
169
+ Injectable,
170
+ NotFoundException,
171
+ } from '@nestjs/common';
172
+ import { UsersService } from '../users.service';
173
+
174
+ @Injectable()
175
+ export class UserExistsPipe implements PipeTransform<string, Promise<User>> {
176
+ constructor(private readonly usersService: UsersService) {}
177
+
178
+ async transform(id: string): Promise<User> {
179
+ const user = await this.usersService.findOne(id);
180
+
181
+ if (!user) {
182
+ throw new NotFoundException(`User with ID ${id} not found`);
183
+ }
184
+
185
+ return user;
186
+ }
187
+ }
188
+
189
+ // Usage - returns User directly, not ID
190
+ @Get(':id')
191
+ findOne(@Param('id', UserExistsPipe) user: User) {
192
+ return user;
193
+ }
194
+ ```
195
+
196
+ ### Trim and Sanitize
197
+
198
+ ```typescript
199
+ // pipes/trim.pipe.ts
200
+ import { PipeTransform, Injectable } from '@nestjs/common';
201
+
202
+ @Injectable()
203
+ export class TrimPipe implements PipeTransform {
204
+ transform(value: unknown) {
205
+ if (typeof value === 'string') {
206
+ return value.trim();
207
+ }
208
+
209
+ if (typeof value === 'object' && value !== null) {
210
+ return this.trimObject(value);
211
+ }
212
+
213
+ return value;
214
+ }
215
+
216
+ private trimObject(obj: Record<string, unknown>): Record<string, unknown> {
217
+ const trimmed: Record<string, unknown> = {};
218
+
219
+ for (const [key, val] of Object.entries(obj)) {
220
+ trimmed[key] = typeof val === 'string' ? val.trim() : val;
221
+ }
222
+
223
+ return trimmed;
224
+ }
225
+ }
226
+ ```
227
+
228
+ ### File Validation Pipe
229
+
230
+ ```typescript
231
+ // pipes/file-validation.pipe.ts
232
+ import { PipeTransform, Injectable, BadRequestException } from '@nestjs/common';
233
+
234
+ interface FileValidationOptions {
235
+ maxSize: number;
236
+ allowedMimeTypes: string[];
237
+ }
238
+
239
+ @Injectable()
240
+ export class FileValidationPipe implements PipeTransform {
241
+ constructor(private readonly options: FileValidationOptions) {}
242
+
243
+ transform(file: Express.Multer.File) {
244
+ if (!file) {
245
+ throw new BadRequestException('File is required');
246
+ }
247
+
248
+ if (file.size > this.options.maxSize) {
249
+ throw new BadRequestException(
250
+ `File size exceeds ${this.options.maxSize / 1024 / 1024}MB`,
251
+ );
252
+ }
253
+
254
+ if (!this.options.allowedMimeTypes.includes(file.mimetype)) {
255
+ throw new BadRequestException(
256
+ `File type ${file.mimetype} is not allowed`,
257
+ );
258
+ }
259
+
260
+ return file;
261
+ }
262
+ }
263
+
264
+ // Usage
265
+ @Post('upload')
266
+ @UseInterceptors(FileInterceptor('file'))
267
+ upload(
268
+ @UploadedFile(
269
+ new FileValidationPipe({
270
+ maxSize: 5 * 1024 * 1024,
271
+ allowedMimeTypes: ['image/jpeg', 'image/png'],
272
+ }),
273
+ )
274
+ file: Express.Multer.File,
275
+ ) {
276
+ return this.uploadService.upload(file);
277
+ }
278
+ ```
279
+
280
+ ## Pipe Binding Scopes
281
+
282
+ ```typescript
283
+ // Parameter level
284
+ @Get(':id')
285
+ findOne(@Param('id', ParseIntPipe) id: number) {}
286
+
287
+ // Method level
288
+ @Post()
289
+ @UsePipes(ValidationPipe)
290
+ create(@Body() dto: CreateDto) {}
291
+
292
+ // Controller level
293
+ @Controller('users')
294
+ @UsePipes(TrimPipe)
295
+ export class UsersController {}
296
+
297
+ // Global level (main.ts)
298
+ app.useGlobalPipes(new ValidationPipe());
299
+
300
+ // Global with DI
301
+ @Module({
302
+ providers: [
303
+ {
304
+ provide: APP_PIPE,
305
+ useClass: ValidationPipe,
306
+ },
307
+ ],
308
+ })
309
+ export class AppModule {}
310
+ ```
311
+
312
+ ## Anti-patterns
313
+
314
+ ```typescript
315
+ // BAD: Pipe with side effects
316
+ @Injectable()
317
+ export class LoggingPipe implements PipeTransform {
318
+ transform(value: unknown) {
319
+ console.log(value); // Use interceptor instead
320
+ this.analyticsService.track(value); // Side effect!
321
+ return value;
322
+ }
323
+ }
324
+
325
+ // GOOD: Pipes are for transformation/validation only
326
+
327
+ // BAD: Heavy async operations in pipe
328
+ @Injectable()
329
+ export class HeavyPipe implements PipeTransform {
330
+ async transform(value: string) {
331
+ await this.externalApi.validate(value); // Too slow
332
+ return value;
333
+ }
334
+ }
335
+
336
+ // GOOD: Keep pipes lightweight, use guards for auth checks
337
+
338
+ // BAD: Not handling validation errors properly
339
+ transform(value: string) {
340
+ return new Date(value); // Crashes on invalid input
341
+ }
342
+
343
+ // GOOD: Proper error handling
344
+ transform(value: string) {
345
+ const date = new Date(value);
346
+ if (isNaN(date.getTime())) {
347
+ throw new BadRequestException('Invalid date');
348
+ }
349
+ return date;
350
+ }
351
+ ```