@malamute/ai-rules 1.0.0 → 1.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 (133) hide show
  1. package/README.md +270 -121
  2. package/bin/cli.js +5 -2
  3. package/configs/_shared/.claude/rules/conventions/documentation.md +324 -0
  4. package/configs/_shared/.claude/rules/conventions/git.md +265 -0
  5. package/configs/_shared/.claude/rules/{performance.md → conventions/performance.md} +1 -1
  6. package/configs/_shared/.claude/rules/conventions/principles.md +334 -0
  7. package/configs/_shared/.claude/rules/devops/ci-cd.md +262 -0
  8. package/configs/_shared/.claude/rules/devops/docker.md +275 -0
  9. package/configs/_shared/.claude/rules/devops/nx.md +194 -0
  10. package/configs/_shared/.claude/rules/domain/backend/api-design.md +203 -0
  11. package/configs/_shared/.claude/rules/lang/csharp/async.md +220 -0
  12. package/configs/_shared/.claude/rules/lang/csharp/csharp.md +314 -0
  13. package/configs/_shared/.claude/rules/lang/csharp/linq.md +210 -0
  14. package/configs/_shared/.claude/rules/lang/python/async.md +337 -0
  15. package/configs/_shared/.claude/rules/lang/python/celery.md +476 -0
  16. package/configs/_shared/.claude/rules/lang/python/config.md +339 -0
  17. package/configs/{python/.claude/rules → _shared/.claude/rules/lang/python}/database/sqlalchemy.md +6 -1
  18. package/configs/_shared/.claude/rules/lang/python/deployment.md +523 -0
  19. package/configs/_shared/.claude/rules/lang/python/error-handling.md +330 -0
  20. package/configs/_shared/.claude/rules/lang/python/migrations.md +421 -0
  21. package/configs/_shared/.claude/rules/lang/python/python.md +172 -0
  22. package/configs/_shared/.claude/rules/lang/python/repository.md +383 -0
  23. package/configs/{python/.claude/rules → _shared/.claude/rules/lang/python}/testing.md +2 -69
  24. package/configs/_shared/.claude/rules/lang/typescript/async.md +447 -0
  25. package/configs/_shared/.claude/rules/lang/typescript/generics.md +356 -0
  26. package/configs/_shared/.claude/rules/lang/typescript/typescript.md +212 -0
  27. package/configs/_shared/.claude/rules/quality/error-handling.md +48 -0
  28. package/configs/_shared/.claude/rules/quality/logging.md +45 -0
  29. package/configs/_shared/.claude/rules/quality/observability.md +240 -0
  30. package/configs/_shared/.claude/rules/quality/testing-patterns.md +65 -0
  31. package/configs/_shared/.claude/rules/security/secrets-management.md +222 -0
  32. package/configs/_shared/.claude/skills/analysis/explore/SKILL.md +257 -0
  33. package/configs/_shared/.claude/skills/analysis/security-audit/SKILL.md +184 -0
  34. package/configs/_shared/.claude/skills/dev/api-endpoint/SKILL.md +126 -0
  35. package/configs/_shared/.claude/{commands/generate-tests.md → skills/dev/generate-tests/SKILL.md} +6 -0
  36. package/configs/_shared/.claude/{commands/fix-issue.md → skills/git/fix-issue/SKILL.md} +6 -0
  37. package/configs/_shared/.claude/{commands/review-pr.md → skills/git/review-pr/SKILL.md} +6 -0
  38. package/configs/_shared/.claude/skills/infra/deploy/SKILL.md +139 -0
  39. package/configs/_shared/.claude/skills/infra/docker/SKILL.md +95 -0
  40. package/configs/_shared/.claude/skills/infra/migration/SKILL.md +158 -0
  41. package/configs/_shared/.claude/skills/nx/nx-affected/SKILL.md +72 -0
  42. package/configs/_shared/.claude/skills/nx/nx-lib/SKILL.md +375 -0
  43. package/configs/_shared/CLAUDE.md +52 -149
  44. package/configs/angular/.claude/rules/{components.md → core/components.md} +69 -15
  45. package/configs/angular/.claude/rules/core/resource.md +285 -0
  46. package/configs/angular/.claude/rules/core/signals.md +323 -0
  47. package/configs/angular/.claude/rules/http.md +338 -0
  48. package/configs/angular/.claude/rules/routing.md +291 -0
  49. package/configs/angular/.claude/rules/ssr.md +312 -0
  50. package/configs/angular/.claude/rules/state/signal-store.md +408 -0
  51. package/configs/angular/.claude/rules/{state.md → state/state.md} +2 -2
  52. package/configs/angular/.claude/rules/testing.md +7 -7
  53. package/configs/angular/.claude/rules/ui/aria.md +422 -0
  54. package/configs/angular/.claude/rules/ui/forms.md +424 -0
  55. package/configs/angular/.claude/rules/ui/pipes-directives.md +335 -0
  56. package/configs/angular/.claude/settings.json +1 -0
  57. package/configs/angular/.claude/skills/ngrx-slice/SKILL.md +362 -0
  58. package/configs/angular/.claude/skills/signal-store/SKILL.md +445 -0
  59. package/configs/angular/CLAUDE.md +24 -216
  60. package/configs/dotnet/.claude/rules/background-services.md +552 -0
  61. package/configs/dotnet/.claude/rules/configuration.md +426 -0
  62. package/configs/dotnet/.claude/rules/ddd.md +447 -0
  63. package/configs/dotnet/.claude/rules/dependency-injection.md +343 -0
  64. package/configs/dotnet/.claude/rules/mediatr.md +320 -0
  65. package/configs/dotnet/.claude/rules/middleware.md +489 -0
  66. package/configs/dotnet/.claude/rules/result-pattern.md +363 -0
  67. package/configs/dotnet/.claude/rules/validation.md +388 -0
  68. package/configs/dotnet/.claude/settings.json +21 -3
  69. package/configs/dotnet/CLAUDE.md +53 -286
  70. package/configs/fastapi/.claude/rules/background-tasks.md +254 -0
  71. package/configs/fastapi/.claude/rules/dependencies.md +170 -0
  72. package/configs/{python → fastapi}/.claude/rules/fastapi.md +61 -1
  73. package/configs/fastapi/.claude/rules/lifespan.md +274 -0
  74. package/configs/fastapi/.claude/rules/middleware.md +229 -0
  75. package/configs/fastapi/.claude/rules/pydantic.md +433 -0
  76. package/configs/fastapi/.claude/rules/responses.md +251 -0
  77. package/configs/fastapi/.claude/rules/routers.md +202 -0
  78. package/configs/fastapi/.claude/rules/security.md +222 -0
  79. package/configs/fastapi/.claude/rules/testing.md +251 -0
  80. package/configs/fastapi/.claude/rules/websockets.md +298 -0
  81. package/configs/fastapi/.claude/settings.json +33 -0
  82. package/configs/fastapi/CLAUDE.md +144 -0
  83. package/configs/flask/.claude/rules/blueprints.md +208 -0
  84. package/configs/flask/.claude/rules/cli.md +285 -0
  85. package/configs/flask/.claude/rules/configuration.md +281 -0
  86. package/configs/flask/.claude/rules/context.md +238 -0
  87. package/configs/flask/.claude/rules/error-handlers.md +278 -0
  88. package/configs/flask/.claude/rules/extensions.md +278 -0
  89. package/configs/flask/.claude/rules/flask.md +171 -0
  90. package/configs/flask/.claude/rules/marshmallow.md +206 -0
  91. package/configs/flask/.claude/rules/security.md +267 -0
  92. package/configs/flask/.claude/rules/testing.md +284 -0
  93. package/configs/flask/.claude/settings.json +33 -0
  94. package/configs/flask/CLAUDE.md +166 -0
  95. package/configs/nestjs/.claude/rules/common-patterns.md +300 -0
  96. package/configs/nestjs/.claude/rules/filters.md +376 -0
  97. package/configs/nestjs/.claude/rules/interceptors.md +317 -0
  98. package/configs/nestjs/.claude/rules/middleware.md +321 -0
  99. package/configs/nestjs/.claude/rules/modules.md +26 -0
  100. package/configs/nestjs/.claude/rules/pipes.md +351 -0
  101. package/configs/nestjs/.claude/rules/websockets.md +451 -0
  102. package/configs/nestjs/.claude/settings.json +16 -2
  103. package/configs/nestjs/CLAUDE.md +57 -215
  104. package/configs/nextjs/.claude/rules/api-routes.md +358 -0
  105. package/configs/nextjs/.claude/rules/authentication.md +355 -0
  106. package/configs/nextjs/.claude/rules/components.md +52 -0
  107. package/configs/nextjs/.claude/rules/data-fetching.md +249 -0
  108. package/configs/nextjs/.claude/rules/database.md +400 -0
  109. package/configs/nextjs/.claude/rules/middleware.md +303 -0
  110. package/configs/nextjs/.claude/rules/routing.md +324 -0
  111. package/configs/nextjs/.claude/rules/seo.md +350 -0
  112. package/configs/nextjs/.claude/rules/server-actions.md +353 -0
  113. package/configs/nextjs/.claude/rules/state/zustand.md +6 -6
  114. package/configs/nextjs/.claude/settings.json +5 -0
  115. package/configs/nextjs/CLAUDE.md +69 -331
  116. package/package.json +23 -9
  117. package/src/cli.js +220 -0
  118. package/src/config.js +29 -0
  119. package/src/index.js +13 -0
  120. package/src/installer.js +361 -0
  121. package/src/merge.js +116 -0
  122. package/src/tech-config.json +29 -0
  123. package/src/utils.js +96 -0
  124. package/configs/python/.claude/rules/flask.md +0 -332
  125. package/configs/python/.claude/settings.json +0 -18
  126. package/configs/python/CLAUDE.md +0 -273
  127. package/src/install.js +0 -315
  128. /package/configs/_shared/.claude/rules/{accessibility.md → domain/frontend/accessibility.md} +0 -0
  129. /package/configs/_shared/.claude/rules/{security.md → security/security.md} +0 -0
  130. /package/configs/_shared/.claude/skills/{debug → dev/debug}/SKILL.md +0 -0
  131. /package/configs/_shared/.claude/skills/{learning → dev/learning}/SKILL.md +0 -0
  132. /package/configs/_shared/.claude/skills/{spec → dev/spec}/SKILL.md +0 -0
  133. /package/configs/_shared/.claude/skills/{review → git/review}/SKILL.md +0 -0
@@ -0,0 +1,317 @@
1
+ ---
2
+ paths:
3
+ - "src/**/*.interceptor.ts"
4
+ - "src/**/interceptors/**/*.ts"
5
+ - "src/**/*.controller.ts"
6
+ - "src/**/*.module.ts"
7
+ ---
8
+
9
+ # NestJS Interceptors
10
+
11
+ ## Interceptor Basics
12
+
13
+ Interceptors can:
14
+ - Transform response data
15
+ - Transform exceptions
16
+ - Extend/override function behavior
17
+ - Implement caching, logging, timeout
18
+
19
+ ```typescript
20
+ import {
21
+ Injectable,
22
+ NestInterceptor,
23
+ ExecutionContext,
24
+ CallHandler,
25
+ } from '@nestjs/common';
26
+ import { Observable } from 'rxjs';
27
+ import { map, tap } from 'rxjs/operators';
28
+
29
+ @Injectable()
30
+ export class LoggingInterceptor implements NestInterceptor {
31
+ intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
32
+ const request = context.switchToHttp().getRequest();
33
+ const { method, url } = request;
34
+ const now = Date.now();
35
+
36
+ console.log(`[${method}] ${url} - Started`);
37
+
38
+ return next.handle().pipe(
39
+ tap(() => {
40
+ console.log(`[${method}] ${url} - ${Date.now() - now}ms`);
41
+ }),
42
+ );
43
+ }
44
+ }
45
+ ```
46
+
47
+ ## Response Transform Interceptor
48
+
49
+ ```typescript
50
+ // Wrap all responses in standard format
51
+ export interface ApiResponse<T> {
52
+ data: T;
53
+ meta: {
54
+ timestamp: string;
55
+ requestId: string;
56
+ };
57
+ }
58
+
59
+ @Injectable()
60
+ export class TransformInterceptor<T> implements NestInterceptor<T, ApiResponse<T>> {
61
+ intercept(context: ExecutionContext, next: CallHandler): Observable<ApiResponse<T>> {
62
+ const request = context.switchToHttp().getRequest();
63
+
64
+ return next.handle().pipe(
65
+ map((data) => ({
66
+ data,
67
+ meta: {
68
+ timestamp: new Date().toISOString(),
69
+ requestId: request.id,
70
+ },
71
+ })),
72
+ );
73
+ }
74
+ }
75
+ ```
76
+
77
+ ## Timeout Interceptor
78
+
79
+ ```typescript
80
+ import { Injectable, RequestTimeoutException } from '@nestjs/common';
81
+ import { Observable, throwError, TimeoutError } from 'rxjs';
82
+ import { catchError, timeout } from 'rxjs/operators';
83
+
84
+ @Injectable()
85
+ export class TimeoutInterceptor implements NestInterceptor {
86
+ constructor(private readonly timeoutMs: number = 30000) {}
87
+
88
+ intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
89
+ return next.handle().pipe(
90
+ timeout(this.timeoutMs),
91
+ catchError((err) => {
92
+ if (err instanceof TimeoutError) {
93
+ return throwError(() => new RequestTimeoutException('Request timeout'));
94
+ }
95
+ return throwError(() => err);
96
+ }),
97
+ );
98
+ }
99
+ }
100
+ ```
101
+
102
+ ## Caching Interceptor
103
+
104
+ ```typescript
105
+ import { CACHE_MANAGER } from '@nestjs/cache-manager';
106
+ import { Cache } from 'cache-manager';
107
+
108
+ @Injectable()
109
+ export class HttpCacheInterceptor implements NestInterceptor {
110
+ constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {}
111
+
112
+ async intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<any>> {
113
+ const request = context.switchToHttp().getRequest();
114
+
115
+ // Only cache GET requests
116
+ if (request.method !== 'GET') {
117
+ return next.handle();
118
+ }
119
+
120
+ const cacheKey = `http:${request.url}`;
121
+ const cachedResponse = await this.cacheManager.get(cacheKey);
122
+
123
+ if (cachedResponse) {
124
+ return of(cachedResponse);
125
+ }
126
+
127
+ return next.handle().pipe(
128
+ tap(async (response) => {
129
+ await this.cacheManager.set(cacheKey, response, 60000); // 60s TTL
130
+ }),
131
+ );
132
+ }
133
+ }
134
+ ```
135
+
136
+ ## Error Mapping Interceptor
137
+
138
+ ```typescript
139
+ @Injectable()
140
+ export class ErrorMappingInterceptor implements NestInterceptor {
141
+ intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
142
+ return next.handle().pipe(
143
+ catchError((error) => {
144
+ // Map domain errors to HTTP errors
145
+ if (error instanceof EntityNotFoundError) {
146
+ return throwError(() => new NotFoundException(error.message));
147
+ }
148
+ if (error instanceof ValidationError) {
149
+ return throwError(() => new BadRequestException(error.errors));
150
+ }
151
+ if (error instanceof UnauthorizedError) {
152
+ return throwError(() => new UnauthorizedException(error.message));
153
+ }
154
+
155
+ // Re-throw unknown errors
156
+ return throwError(() => error);
157
+ }),
158
+ );
159
+ }
160
+ }
161
+ ```
162
+
163
+ ## Serialization Interceptor
164
+
165
+ ```typescript
166
+ import { ClassSerializerInterceptor, PlainLiteralObject } from '@nestjs/common';
167
+ import { ClassTransformOptions, plainToClass } from 'class-transformer';
168
+
169
+ @Injectable()
170
+ export class CustomSerializerInterceptor extends ClassSerializerInterceptor {
171
+ transformToPlain(
172
+ data: object,
173
+ options: ClassTransformOptions,
174
+ ): PlainLiteralObject | PlainLiteralObject[] {
175
+ // Add custom serialization logic
176
+ return super.transformToPlain(data, {
177
+ ...options,
178
+ excludeExtraneousValues: true, // Only @Expose() fields
179
+ });
180
+ }
181
+ }
182
+
183
+ // DTO with explicit exposure
184
+ export class UserResponseDto {
185
+ @Expose()
186
+ id: string;
187
+
188
+ @Expose()
189
+ email: string;
190
+
191
+ @Expose()
192
+ name: string;
193
+
194
+ @Exclude()
195
+ password: string; // Never exposed
196
+
197
+ @Expose()
198
+ @Transform(({ value }) => value.toISOString())
199
+ createdAt: Date;
200
+ }
201
+ ```
202
+
203
+ ## Audit Log Interceptor
204
+
205
+ ```typescript
206
+ @Injectable()
207
+ export class AuditLogInterceptor implements NestInterceptor {
208
+ constructor(private readonly auditService: AuditService) {}
209
+
210
+ intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
211
+ const request = context.switchToHttp().getRequest();
212
+ const { method, url, user, body } = request;
213
+
214
+ // Only audit mutating operations
215
+ if (['POST', 'PUT', 'PATCH', 'DELETE'].includes(method)) {
216
+ return next.handle().pipe(
217
+ tap({
218
+ next: (response) => {
219
+ this.auditService.log({
220
+ userId: user?.id,
221
+ action: method,
222
+ resource: url,
223
+ input: this.sanitize(body),
224
+ output: this.sanitize(response),
225
+ timestamp: new Date(),
226
+ });
227
+ },
228
+ error: (error) => {
229
+ this.auditService.log({
230
+ userId: user?.id,
231
+ action: method,
232
+ resource: url,
233
+ input: this.sanitize(body),
234
+ error: error.message,
235
+ timestamp: new Date(),
236
+ });
237
+ },
238
+ }),
239
+ );
240
+ }
241
+
242
+ return next.handle();
243
+ }
244
+
245
+ private sanitize(data: Record<string, unknown> | null): Record<string, unknown> | null {
246
+ if (!data) return data;
247
+ const { password, token, secret, ...safe } = data;
248
+ return safe;
249
+ }
250
+ }
251
+ ```
252
+
253
+ ## Applying Interceptors
254
+
255
+ ```typescript
256
+ // Global (main.ts)
257
+ app.useGlobalInterceptors(new LoggingInterceptor());
258
+
259
+ // Global with DI (module)
260
+ @Module({
261
+ providers: [
262
+ {
263
+ provide: APP_INTERCEPTOR,
264
+ useClass: TransformInterceptor,
265
+ },
266
+ ],
267
+ })
268
+ export class AppModule {}
269
+
270
+ // Controller level
271
+ @UseInterceptors(LoggingInterceptor)
272
+ @Controller('users')
273
+ export class UsersController {}
274
+
275
+ // Method level
276
+ @UseInterceptors(CacheInterceptor)
277
+ @Get(':id')
278
+ findOne(@Param('id') id: string) {}
279
+
280
+ // Multiple interceptors (order matters: first to last)
281
+ @UseInterceptors(LoggingInterceptor, TransformInterceptor)
282
+ @Controller('users')
283
+ export class UsersController {}
284
+ ```
285
+
286
+ ## Custom Decorator for Interceptors
287
+
288
+ ```typescript
289
+ // Skip certain interceptors conditionally
290
+ export const SKIP_TRANSFORM = 'skipTransform';
291
+ export const SkipTransform = () => SetMetadata(SKIP_TRANSFORM, true);
292
+
293
+ @Injectable()
294
+ export class TransformInterceptor implements NestInterceptor {
295
+ constructor(private reflector: Reflector) {}
296
+
297
+ intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
298
+ const skipTransform = this.reflector.get<boolean>(
299
+ SKIP_TRANSFORM,
300
+ context.getHandler(),
301
+ );
302
+
303
+ if (skipTransform) {
304
+ return next.handle();
305
+ }
306
+
307
+ return next.handle().pipe(map((data) => ({ data })));
308
+ }
309
+ }
310
+
311
+ // Usage
312
+ @SkipTransform()
313
+ @Get('raw')
314
+ getRawData() {
315
+ return { raw: true };
316
+ }
317
+ ```
@@ -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