@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.
- package/README.md +270 -121
- package/bin/cli.js +5 -2
- package/configs/_shared/.claude/rules/conventions/documentation.md +324 -0
- package/configs/_shared/.claude/rules/conventions/git.md +265 -0
- package/configs/_shared/.claude/rules/{performance.md → conventions/performance.md} +1 -1
- package/configs/_shared/.claude/rules/conventions/principles.md +334 -0
- package/configs/_shared/.claude/rules/devops/ci-cd.md +262 -0
- package/configs/_shared/.claude/rules/devops/docker.md +275 -0
- package/configs/_shared/.claude/rules/devops/nx.md +194 -0
- package/configs/_shared/.claude/rules/domain/backend/api-design.md +203 -0
- package/configs/_shared/.claude/rules/lang/csharp/async.md +220 -0
- package/configs/_shared/.claude/rules/lang/csharp/csharp.md +314 -0
- package/configs/_shared/.claude/rules/lang/csharp/linq.md +210 -0
- package/configs/_shared/.claude/rules/lang/python/async.md +337 -0
- package/configs/_shared/.claude/rules/lang/python/celery.md +476 -0
- package/configs/_shared/.claude/rules/lang/python/config.md +339 -0
- package/configs/{python/.claude/rules → _shared/.claude/rules/lang/python}/database/sqlalchemy.md +6 -1
- package/configs/_shared/.claude/rules/lang/python/deployment.md +523 -0
- package/configs/_shared/.claude/rules/lang/python/error-handling.md +330 -0
- package/configs/_shared/.claude/rules/lang/python/migrations.md +421 -0
- package/configs/_shared/.claude/rules/lang/python/python.md +172 -0
- package/configs/_shared/.claude/rules/lang/python/repository.md +383 -0
- package/configs/{python/.claude/rules → _shared/.claude/rules/lang/python}/testing.md +2 -69
- package/configs/_shared/.claude/rules/lang/typescript/async.md +447 -0
- package/configs/_shared/.claude/rules/lang/typescript/generics.md +356 -0
- package/configs/_shared/.claude/rules/lang/typescript/typescript.md +212 -0
- package/configs/_shared/.claude/rules/quality/error-handling.md +48 -0
- package/configs/_shared/.claude/rules/quality/logging.md +45 -0
- package/configs/_shared/.claude/rules/quality/observability.md +240 -0
- package/configs/_shared/.claude/rules/quality/testing-patterns.md +65 -0
- package/configs/_shared/.claude/rules/security/secrets-management.md +222 -0
- package/configs/_shared/.claude/skills/analysis/explore/SKILL.md +257 -0
- package/configs/_shared/.claude/skills/analysis/security-audit/SKILL.md +184 -0
- package/configs/_shared/.claude/skills/dev/api-endpoint/SKILL.md +126 -0
- package/configs/_shared/.claude/{commands/generate-tests.md → skills/dev/generate-tests/SKILL.md} +6 -0
- package/configs/_shared/.claude/{commands/fix-issue.md → skills/git/fix-issue/SKILL.md} +6 -0
- package/configs/_shared/.claude/{commands/review-pr.md → skills/git/review-pr/SKILL.md} +6 -0
- package/configs/_shared/.claude/skills/infra/deploy/SKILL.md +139 -0
- package/configs/_shared/.claude/skills/infra/docker/SKILL.md +95 -0
- package/configs/_shared/.claude/skills/infra/migration/SKILL.md +158 -0
- package/configs/_shared/.claude/skills/nx/nx-affected/SKILL.md +72 -0
- package/configs/_shared/.claude/skills/nx/nx-lib/SKILL.md +375 -0
- package/configs/_shared/CLAUDE.md +52 -149
- package/configs/angular/.claude/rules/{components.md → core/components.md} +69 -15
- package/configs/angular/.claude/rules/core/resource.md +285 -0
- package/configs/angular/.claude/rules/core/signals.md +323 -0
- package/configs/angular/.claude/rules/http.md +338 -0
- package/configs/angular/.claude/rules/routing.md +291 -0
- package/configs/angular/.claude/rules/ssr.md +312 -0
- package/configs/angular/.claude/rules/state/signal-store.md +408 -0
- package/configs/angular/.claude/rules/{state.md → state/state.md} +2 -2
- package/configs/angular/.claude/rules/testing.md +7 -7
- package/configs/angular/.claude/rules/ui/aria.md +422 -0
- package/configs/angular/.claude/rules/ui/forms.md +424 -0
- package/configs/angular/.claude/rules/ui/pipes-directives.md +335 -0
- package/configs/angular/.claude/settings.json +1 -0
- package/configs/angular/.claude/skills/ngrx-slice/SKILL.md +362 -0
- package/configs/angular/.claude/skills/signal-store/SKILL.md +445 -0
- package/configs/angular/CLAUDE.md +24 -216
- package/configs/dotnet/.claude/rules/background-services.md +552 -0
- package/configs/dotnet/.claude/rules/configuration.md +426 -0
- package/configs/dotnet/.claude/rules/ddd.md +447 -0
- package/configs/dotnet/.claude/rules/dependency-injection.md +343 -0
- package/configs/dotnet/.claude/rules/mediatr.md +320 -0
- package/configs/dotnet/.claude/rules/middleware.md +489 -0
- package/configs/dotnet/.claude/rules/result-pattern.md +363 -0
- package/configs/dotnet/.claude/rules/validation.md +388 -0
- package/configs/dotnet/.claude/settings.json +21 -3
- package/configs/dotnet/CLAUDE.md +53 -286
- package/configs/fastapi/.claude/rules/background-tasks.md +254 -0
- package/configs/fastapi/.claude/rules/dependencies.md +170 -0
- package/configs/{python → fastapi}/.claude/rules/fastapi.md +61 -1
- package/configs/fastapi/.claude/rules/lifespan.md +274 -0
- package/configs/fastapi/.claude/rules/middleware.md +229 -0
- package/configs/fastapi/.claude/rules/pydantic.md +433 -0
- package/configs/fastapi/.claude/rules/responses.md +251 -0
- package/configs/fastapi/.claude/rules/routers.md +202 -0
- package/configs/fastapi/.claude/rules/security.md +222 -0
- package/configs/fastapi/.claude/rules/testing.md +251 -0
- package/configs/fastapi/.claude/rules/websockets.md +298 -0
- package/configs/fastapi/.claude/settings.json +33 -0
- package/configs/fastapi/CLAUDE.md +144 -0
- package/configs/flask/.claude/rules/blueprints.md +208 -0
- package/configs/flask/.claude/rules/cli.md +285 -0
- package/configs/flask/.claude/rules/configuration.md +281 -0
- package/configs/flask/.claude/rules/context.md +238 -0
- package/configs/flask/.claude/rules/error-handlers.md +278 -0
- package/configs/flask/.claude/rules/extensions.md +278 -0
- package/configs/flask/.claude/rules/flask.md +171 -0
- package/configs/flask/.claude/rules/marshmallow.md +206 -0
- package/configs/flask/.claude/rules/security.md +267 -0
- package/configs/flask/.claude/rules/testing.md +284 -0
- package/configs/flask/.claude/settings.json +33 -0
- package/configs/flask/CLAUDE.md +166 -0
- package/configs/nestjs/.claude/rules/common-patterns.md +300 -0
- package/configs/nestjs/.claude/rules/filters.md +376 -0
- package/configs/nestjs/.claude/rules/interceptors.md +317 -0
- package/configs/nestjs/.claude/rules/middleware.md +321 -0
- package/configs/nestjs/.claude/rules/modules.md +26 -0
- package/configs/nestjs/.claude/rules/pipes.md +351 -0
- package/configs/nestjs/.claude/rules/websockets.md +451 -0
- package/configs/nestjs/.claude/settings.json +16 -2
- package/configs/nestjs/CLAUDE.md +57 -215
- package/configs/nextjs/.claude/rules/api-routes.md +358 -0
- package/configs/nextjs/.claude/rules/authentication.md +355 -0
- package/configs/nextjs/.claude/rules/components.md +52 -0
- package/configs/nextjs/.claude/rules/data-fetching.md +249 -0
- package/configs/nextjs/.claude/rules/database.md +400 -0
- package/configs/nextjs/.claude/rules/middleware.md +303 -0
- package/configs/nextjs/.claude/rules/routing.md +324 -0
- package/configs/nextjs/.claude/rules/seo.md +350 -0
- package/configs/nextjs/.claude/rules/server-actions.md +353 -0
- package/configs/nextjs/.claude/rules/state/zustand.md +6 -6
- package/configs/nextjs/.claude/settings.json +5 -0
- package/configs/nextjs/CLAUDE.md +69 -331
- package/package.json +23 -9
- package/src/cli.js +220 -0
- package/src/config.js +29 -0
- package/src/index.js +13 -0
- package/src/installer.js +361 -0
- package/src/merge.js +116 -0
- package/src/tech-config.json +29 -0
- package/src/utils.js +96 -0
- package/configs/python/.claude/rules/flask.md +0 -332
- package/configs/python/.claude/settings.json +0 -18
- package/configs/python/CLAUDE.md +0 -273
- package/src/install.js +0 -315
- /package/configs/_shared/.claude/rules/{accessibility.md → domain/frontend/accessibility.md} +0 -0
- /package/configs/_shared/.claude/rules/{security.md → security/security.md} +0 -0
- /package/configs/_shared/.claude/skills/{debug → dev/debug}/SKILL.md +0 -0
- /package/configs/_shared/.claude/skills/{learning → dev/learning}/SKILL.md +0 -0
- /package/configs/_shared/.claude/skills/{spec → dev/spec}/SKILL.md +0 -0
- /package/configs/_shared/.claude/skills/{review → git/review}/SKILL.md +0 -0
package/configs/nestjs/CLAUDE.md
CHANGED
|
@@ -4,260 +4,102 @@
|
|
|
4
4
|
|
|
5
5
|
## Stack
|
|
6
6
|
|
|
7
|
-
- NestJS
|
|
7
|
+
- NestJS 11+
|
|
8
8
|
- TypeScript strict mode
|
|
9
9
|
- Node.js 20+
|
|
10
|
-
-
|
|
11
|
-
-
|
|
10
|
+
- Vitest + Supertest
|
|
11
|
+
- Prisma or TypeORM
|
|
12
12
|
|
|
13
|
-
## Architecture
|
|
14
|
-
|
|
15
|
-
### Modular Monolith (Recommended)
|
|
13
|
+
## Architecture - Modular Monolith
|
|
16
14
|
|
|
17
15
|
```
|
|
18
16
|
src/
|
|
19
17
|
├── modules/
|
|
20
|
-
│ ├──
|
|
21
|
-
│ │ ├──
|
|
22
|
-
│ │ ├──
|
|
23
|
-
│ │ ├──
|
|
18
|
+
│ ├── [feature]/
|
|
19
|
+
│ │ ├── [feature].module.ts
|
|
20
|
+
│ │ ├── [feature].controller.ts
|
|
21
|
+
│ │ ├── [feature].service.ts
|
|
22
|
+
│ │ ├── [feature].repository.ts
|
|
24
23
|
│ │ ├── dto/
|
|
25
|
-
│ │
|
|
26
|
-
│
|
|
27
|
-
│
|
|
28
|
-
│
|
|
29
|
-
│ │ └── users.repository.ts
|
|
30
|
-
│ │
|
|
31
|
-
│ ├── auth/
|
|
32
|
-
│ │ ├── auth.module.ts
|
|
33
|
-
│ │ ├── auth.controller.ts
|
|
34
|
-
│ │ ├── auth.service.ts
|
|
35
|
-
│ │ ├── strategies/
|
|
36
|
-
│ │ │ ├── jwt.strategy.ts
|
|
37
|
-
│ │ │ └── local.strategy.ts
|
|
38
|
-
│ │ └── guards/
|
|
39
|
-
│ │ └── jwt-auth.guard.ts
|
|
40
|
-
│ │
|
|
41
|
-
│ └── [feature]/
|
|
42
|
-
│ └── ...
|
|
43
|
-
│
|
|
24
|
+
│ │ └── entities/
|
|
25
|
+
│ └── auth/
|
|
26
|
+
│ ├── strategies/
|
|
27
|
+
│ └── guards/
|
|
44
28
|
├── common/
|
|
45
29
|
│ ├── decorators/
|
|
46
30
|
│ ├── filters/
|
|
47
31
|
│ ├── guards/
|
|
48
32
|
│ ├── interceptors/
|
|
49
33
|
│ └── pipes/
|
|
50
|
-
│
|
|
51
34
|
├── config/
|
|
52
|
-
│ ├── config.module.ts
|
|
53
|
-
│ └── database.config.ts
|
|
54
|
-
│
|
|
55
35
|
├── app.module.ts
|
|
56
36
|
└── main.ts
|
|
57
37
|
```
|
|
58
38
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
- **Single Responsibility**: Each module owns one domain
|
|
62
|
-
- **Clear Boundaries**: Modules communicate via exported services
|
|
63
|
-
- **In-Memory Communication**: Direct method calls, not HTTP between modules
|
|
64
|
-
- **Barrel Exports**: Use `index.ts` for clean imports
|
|
39
|
+
## Request Lifecycle
|
|
65
40
|
|
|
66
|
-
|
|
41
|
+
```
|
|
42
|
+
Request → Middleware → Guard → Interceptor (pre) → Pipe → Controller → Interceptor (post) → Response
|
|
43
|
+
```
|
|
67
44
|
|
|
68
|
-
|
|
45
|
+
## Core Principles
|
|
69
46
|
|
|
70
|
-
|
|
71
|
-
- Delegate business logic to services
|
|
72
|
-
- Use DTOs for all inputs
|
|
73
|
-
- Return consistent response shapes
|
|
47
|
+
### Module Design
|
|
74
48
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
constructor(private readonly usersService: UsersService) {}
|
|
49
|
+
- **Single Responsibility**: One module = one domain
|
|
50
|
+
- **Clear Boundaries**: Communicate via exported services only
|
|
51
|
+
- **Barrel Exports**: Use `index.ts` for clean imports
|
|
79
52
|
|
|
80
|
-
|
|
81
|
-
@HttpCode(HttpStatus.CREATED)
|
|
82
|
-
create(@Body() createUserDto: CreateUserDto): Promise<User> {
|
|
83
|
-
return this.usersService.create(createUserDto);
|
|
84
|
-
}
|
|
53
|
+
### Layer Responsibilities
|
|
85
54
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
55
|
+
| Layer | Responsibility |
|
|
56
|
+
|-------|---------------|
|
|
57
|
+
| Controller | HTTP only, delegates to service |
|
|
58
|
+
| Service | All business logic |
|
|
59
|
+
| Repository | Data access only |
|
|
60
|
+
| DTO | Validation with class-validator |
|
|
92
61
|
|
|
93
|
-
###
|
|
62
|
+
### Exception Handling
|
|
94
63
|
|
|
95
|
-
-
|
|
96
|
-
-
|
|
97
|
-
-
|
|
64
|
+
Use built-in NestJS exceptions:
|
|
65
|
+
- `NotFoundException` - 404
|
|
66
|
+
- `BadRequestException` - 400
|
|
67
|
+
- `UnauthorizedException` - 401
|
|
68
|
+
- `ForbiddenException` - 403
|
|
69
|
+
- `ConflictException` - 409
|
|
98
70
|
|
|
99
|
-
|
|
100
|
-
@Injectable()
|
|
101
|
-
export class UsersService {
|
|
102
|
-
constructor(private readonly usersRepository: UsersRepository) {}
|
|
71
|
+
### Validation (Global)
|
|
103
72
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
return user;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
```
|
|
73
|
+
Enable global `ValidationPipe` in main.ts with:
|
|
74
|
+
- `whitelist: true` - Strip non-whitelisted properties
|
|
75
|
+
- `forbidNonWhitelisted: true` - Throw on extra properties
|
|
76
|
+
- `transform: true` - Auto-transform to DTO types
|
|
113
77
|
|
|
114
78
|
### DTOs
|
|
115
79
|
|
|
116
80
|
- Always use class-validator decorators
|
|
117
|
-
-
|
|
118
|
-
-
|
|
81
|
+
- `PartialType`, `PickType`, `OmitType` for variants
|
|
82
|
+
- Combine with Swagger decorators
|
|
119
83
|
|
|
120
|
-
|
|
121
|
-
export class CreateUserDto {
|
|
122
|
-
@IsEmail()
|
|
123
|
-
@IsNotEmpty()
|
|
124
|
-
email: string;
|
|
84
|
+
## Authentication
|
|
125
85
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
@IsString()
|
|
131
|
-
@IsOptional()
|
|
132
|
-
name?: string;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export class UpdateUserDto extends PartialType(CreateUserDto) {}
|
|
136
|
-
```
|
|
86
|
+
- Passport.js + JWT strategy
|
|
87
|
+
- Global `JwtAuthGuard` with `@Public()` decorator for exceptions
|
|
88
|
+
- `@CurrentUser()` decorator for accessing user
|
|
137
89
|
|
|
138
90
|
## Commands
|
|
139
91
|
|
|
140
92
|
```bash
|
|
141
|
-
#
|
|
142
|
-
npm run
|
|
143
|
-
|
|
144
|
-
#
|
|
145
|
-
npm run
|
|
146
|
-
|
|
147
|
-
# Tests
|
|
148
|
-
npm run test # Unit tests
|
|
149
|
-
npm run test:watch # Watch mode
|
|
150
|
-
npm run test:cov # Coverage
|
|
151
|
-
npm run test:e2e # E2E tests
|
|
152
|
-
|
|
153
|
-
# Linting
|
|
154
|
-
npm run lint
|
|
155
|
-
npm run format
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
## Global Setup (main.ts)
|
|
159
|
-
|
|
160
|
-
```typescript
|
|
161
|
-
async function bootstrap() {
|
|
162
|
-
const app = await NestFactory.create(AppModule);
|
|
163
|
-
|
|
164
|
-
// Global validation pipe
|
|
165
|
-
app.useGlobalPipes(
|
|
166
|
-
new ValidationPipe({
|
|
167
|
-
whitelist: true,
|
|
168
|
-
forbidNonWhitelisted: true,
|
|
169
|
-
transform: true,
|
|
170
|
-
transformOptions: {
|
|
171
|
-
enableImplicitConversion: true,
|
|
172
|
-
},
|
|
173
|
-
}),
|
|
174
|
-
);
|
|
175
|
-
|
|
176
|
-
// Global prefix
|
|
177
|
-
app.setGlobalPrefix('api/v1');
|
|
178
|
-
|
|
179
|
-
// CORS
|
|
180
|
-
app.enableCors();
|
|
181
|
-
|
|
182
|
-
// Swagger (dev only)
|
|
183
|
-
if (process.env.NODE_ENV !== 'production') {
|
|
184
|
-
const config = new DocumentBuilder()
|
|
185
|
-
.setTitle('API')
|
|
186
|
-
.setVersion('1.0')
|
|
187
|
-
.addBearerAuth()
|
|
188
|
-
.build();
|
|
189
|
-
const document = SwaggerModule.createDocument(app, config);
|
|
190
|
-
SwaggerModule.setup('docs', app, document);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
await app.listen(process.env.PORT ?? 3000);
|
|
194
|
-
}
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
## Common Patterns
|
|
198
|
-
|
|
199
|
-
### Custom Decorator
|
|
200
|
-
|
|
201
|
-
```typescript
|
|
202
|
-
// current-user.decorator.ts
|
|
203
|
-
export const CurrentUser = createParamDecorator(
|
|
204
|
-
(data: keyof User | undefined, ctx: ExecutionContext) => {
|
|
205
|
-
const request = ctx.switchToHttp().getRequest();
|
|
206
|
-
const user = request.user;
|
|
207
|
-
return data ? user?.[data] : user;
|
|
208
|
-
},
|
|
209
|
-
);
|
|
210
|
-
|
|
211
|
-
// Usage
|
|
212
|
-
@Get('profile')
|
|
213
|
-
getProfile(@CurrentUser() user: User) { ... }
|
|
93
|
+
npm run start:dev # Dev with watch
|
|
94
|
+
npm run build # Production build
|
|
95
|
+
npm run test # Unit tests
|
|
96
|
+
npm run test:e2e # E2E tests
|
|
97
|
+
npm run test:cov # Coverage
|
|
214
98
|
```
|
|
215
99
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
```typescript
|
|
219
|
-
@Catch()
|
|
220
|
-
export class AllExceptionsFilter implements ExceptionFilter {
|
|
221
|
-
catch(exception: unknown, host: ArgumentsHost) {
|
|
222
|
-
const ctx = host.switchToHttp();
|
|
223
|
-
const response = ctx.getResponse<Response>();
|
|
224
|
-
|
|
225
|
-
const status =
|
|
226
|
-
exception instanceof HttpException
|
|
227
|
-
? exception.getStatus()
|
|
228
|
-
: HttpStatus.INTERNAL_SERVER_ERROR;
|
|
229
|
-
|
|
230
|
-
const message =
|
|
231
|
-
exception instanceof HttpException
|
|
232
|
-
? exception.message
|
|
233
|
-
: 'Internal server error';
|
|
234
|
-
|
|
235
|
-
response.status(status).json({
|
|
236
|
-
statusCode: status,
|
|
237
|
-
message,
|
|
238
|
-
timestamp: new Date().toISOString(),
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
### Response Interceptor
|
|
100
|
+
## Code Style
|
|
245
101
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
{
|
|
251
|
-
intercept(
|
|
252
|
-
context: ExecutionContext,
|
|
253
|
-
next: CallHandler,
|
|
254
|
-
): Observable<Response<T>> {
|
|
255
|
-
return next.handle().pipe(
|
|
256
|
-
map((data) => ({
|
|
257
|
-
success: true,
|
|
258
|
-
data,
|
|
259
|
-
})),
|
|
260
|
-
);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
```
|
|
102
|
+
- Constructor injection (not property injection)
|
|
103
|
+
- `readonly` for injected dependencies
|
|
104
|
+
- Async methods return `Promise<T>`
|
|
105
|
+
- Use `ParseUUIDPipe`, `ParseIntPipe` for params
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "app/api/**/*.ts"
|
|
4
|
+
- "src/app/api/**/*.ts"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Next.js API Routes (App Router)
|
|
8
|
+
|
|
9
|
+
## Basic Route Handler
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
// app/api/users/route.ts
|
|
13
|
+
import { NextResponse } from 'next/server';
|
|
14
|
+
|
|
15
|
+
export async function GET() {
|
|
16
|
+
const users = await db.user.findMany();
|
|
17
|
+
return NextResponse.json(users);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function POST(request: Request) {
|
|
21
|
+
const body = await request.json();
|
|
22
|
+
const user = await db.user.create({ data: body });
|
|
23
|
+
return NextResponse.json(user, { status: 201 });
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Dynamic Routes
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
// app/api/users/[id]/route.ts
|
|
31
|
+
import { NextResponse } from 'next/server';
|
|
32
|
+
|
|
33
|
+
export async function GET(
|
|
34
|
+
request: Request,
|
|
35
|
+
{ params }: { params: Promise<{ id: string }> }
|
|
36
|
+
) {
|
|
37
|
+
const { id } = await params;
|
|
38
|
+
const user = await db.user.findUnique({ where: { id } });
|
|
39
|
+
|
|
40
|
+
if (!user) {
|
|
41
|
+
return NextResponse.json(
|
|
42
|
+
{ error: 'User not found' },
|
|
43
|
+
{ status: 404 }
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return NextResponse.json(user);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function PUT(
|
|
51
|
+
request: Request,
|
|
52
|
+
{ params }: { params: Promise<{ id: string }> }
|
|
53
|
+
) {
|
|
54
|
+
const { id } = await params;
|
|
55
|
+
const body = await request.json();
|
|
56
|
+
|
|
57
|
+
const user = await db.user.update({
|
|
58
|
+
where: { id },
|
|
59
|
+
data: body,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return NextResponse.json(user);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function DELETE(
|
|
66
|
+
request: Request,
|
|
67
|
+
{ params }: { params: Promise<{ id: string }> }
|
|
68
|
+
) {
|
|
69
|
+
const { id } = await params;
|
|
70
|
+
await db.user.delete({ where: { id } });
|
|
71
|
+
return new NextResponse(null, { status: 204 });
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Request Handling
|
|
76
|
+
|
|
77
|
+
### Query Parameters
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
export async function GET(request: Request) {
|
|
81
|
+
const { searchParams } = new URL(request.url);
|
|
82
|
+
const page = parseInt(searchParams.get('page') || '1');
|
|
83
|
+
const limit = parseInt(searchParams.get('limit') || '10');
|
|
84
|
+
const search = searchParams.get('search') || '';
|
|
85
|
+
|
|
86
|
+
const users = await db.user.findMany({
|
|
87
|
+
where: { name: { contains: search } },
|
|
88
|
+
skip: (page - 1) * limit,
|
|
89
|
+
take: limit,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const total = await db.user.count({
|
|
93
|
+
where: { name: { contains: search } },
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return NextResponse.json({
|
|
97
|
+
data: users,
|
|
98
|
+
meta: {
|
|
99
|
+
page,
|
|
100
|
+
limit,
|
|
101
|
+
total,
|
|
102
|
+
totalPages: Math.ceil(total / limit),
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Headers & Cookies
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
import { cookies, headers } from 'next/headers';
|
|
112
|
+
|
|
113
|
+
export async function GET() {
|
|
114
|
+
const headersList = await headers();
|
|
115
|
+
const authHeader = headersList.get('authorization');
|
|
116
|
+
|
|
117
|
+
const cookieStore = await cookies();
|
|
118
|
+
const token = cookieStore.get('token');
|
|
119
|
+
|
|
120
|
+
return NextResponse.json({ auth: !!authHeader, hasToken: !!token });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export async function POST() {
|
|
124
|
+
const response = NextResponse.json({ success: true });
|
|
125
|
+
|
|
126
|
+
// Set cookie
|
|
127
|
+
response.cookies.set('session', 'value', {
|
|
128
|
+
httpOnly: true,
|
|
129
|
+
secure: process.env.NODE_ENV === 'production',
|
|
130
|
+
sameSite: 'lax',
|
|
131
|
+
maxAge: 60 * 60 * 24 * 7, // 1 week
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
return response;
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Validation with Zod
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
// app/api/users/route.ts
|
|
142
|
+
import { NextResponse } from 'next/server';
|
|
143
|
+
import { z } from 'zod';
|
|
144
|
+
|
|
145
|
+
const createUserSchema = z.object({
|
|
146
|
+
email: z.string().email(),
|
|
147
|
+
name: z.string().min(2).max(100),
|
|
148
|
+
age: z.number().min(18).optional(),
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
export async function POST(request: Request) {
|
|
152
|
+
const body = await request.json();
|
|
153
|
+
|
|
154
|
+
const result = createUserSchema.safeParse(body);
|
|
155
|
+
|
|
156
|
+
if (!result.success) {
|
|
157
|
+
return NextResponse.json(
|
|
158
|
+
{
|
|
159
|
+
type: 'validation_error',
|
|
160
|
+
title: 'Validation Error',
|
|
161
|
+
status: 400,
|
|
162
|
+
errors: result.error.issues.map(issue => ({
|
|
163
|
+
field: issue.path.join('.'),
|
|
164
|
+
message: issue.message,
|
|
165
|
+
})),
|
|
166
|
+
},
|
|
167
|
+
{ status: 400 }
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const user = await db.user.create({ data: result.data });
|
|
172
|
+
return NextResponse.json(user, { status: 201 });
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Error Handling
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
// lib/api-error.ts
|
|
180
|
+
export class ApiError extends Error {
|
|
181
|
+
constructor(
|
|
182
|
+
public status: number,
|
|
183
|
+
message: string,
|
|
184
|
+
public code?: string
|
|
185
|
+
) {
|
|
186
|
+
super(message);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// app/api/users/[id]/route.ts
|
|
191
|
+
import { ApiError } from '@/lib/api-error';
|
|
192
|
+
|
|
193
|
+
export async function GET(
|
|
194
|
+
request: Request,
|
|
195
|
+
{ params }: { params: Promise<{ id: string }> }
|
|
196
|
+
) {
|
|
197
|
+
try {
|
|
198
|
+
const { id } = await params;
|
|
199
|
+
const user = await db.user.findUnique({ where: { id } });
|
|
200
|
+
|
|
201
|
+
if (!user) {
|
|
202
|
+
throw new ApiError(404, 'User not found', 'USER_NOT_FOUND');
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return NextResponse.json(user);
|
|
206
|
+
} catch (error) {
|
|
207
|
+
if (error instanceof ApiError) {
|
|
208
|
+
return NextResponse.json(
|
|
209
|
+
{
|
|
210
|
+
type: `https://api.example.com/errors/${error.code}`,
|
|
211
|
+
title: error.message,
|
|
212
|
+
status: error.status,
|
|
213
|
+
},
|
|
214
|
+
{ status: error.status }
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
console.error('Unexpected error:', error);
|
|
219
|
+
return NextResponse.json(
|
|
220
|
+
{ title: 'Internal Server Error', status: 500 },
|
|
221
|
+
{ status: 500 }
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Authentication
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
import { auth } from '@/auth';
|
|
231
|
+
|
|
232
|
+
export async function GET() {
|
|
233
|
+
const session = await auth();
|
|
234
|
+
|
|
235
|
+
if (!session) {
|
|
236
|
+
return NextResponse.json(
|
|
237
|
+
{ error: 'Unauthorized' },
|
|
238
|
+
{ status: 401 }
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const users = await db.user.findMany({
|
|
243
|
+
where: { organizationId: session.user.organizationId },
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
return NextResponse.json(users);
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## File Upload
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
// app/api/upload/route.ts
|
|
254
|
+
import { writeFile } from 'fs/promises';
|
|
255
|
+
import { NextResponse } from 'next/server';
|
|
256
|
+
|
|
257
|
+
export async function POST(request: Request) {
|
|
258
|
+
const formData = await request.formData();
|
|
259
|
+
const file = formData.get('file') as File | null;
|
|
260
|
+
|
|
261
|
+
if (!file) {
|
|
262
|
+
return NextResponse.json(
|
|
263
|
+
{ error: 'No file uploaded' },
|
|
264
|
+
{ status: 400 }
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Validate file
|
|
269
|
+
const maxSize = 5 * 1024 * 1024; // 5MB
|
|
270
|
+
if (file.size > maxSize) {
|
|
271
|
+
return NextResponse.json(
|
|
272
|
+
{ error: 'File too large' },
|
|
273
|
+
{ status: 400 }
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const allowedTypes = ['image/jpeg', 'image/png', 'image/webp'];
|
|
278
|
+
if (!allowedTypes.includes(file.type)) {
|
|
279
|
+
return NextResponse.json(
|
|
280
|
+
{ error: 'Invalid file type' },
|
|
281
|
+
{ status: 400 }
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const bytes = await file.arrayBuffer();
|
|
286
|
+
const buffer = Buffer.from(bytes);
|
|
287
|
+
|
|
288
|
+
const filename = `${Date.now()}-${file.name}`;
|
|
289
|
+
await writeFile(`./public/uploads/${filename}`, buffer);
|
|
290
|
+
|
|
291
|
+
return NextResponse.json({ url: `/uploads/${filename}` });
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Streaming Response
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
export async function GET() {
|
|
299
|
+
const encoder = new TextEncoder();
|
|
300
|
+
|
|
301
|
+
const stream = new ReadableStream({
|
|
302
|
+
async start(controller) {
|
|
303
|
+
for (let i = 0; i < 10; i++) {
|
|
304
|
+
controller.enqueue(encoder.encode(`data: ${i}\n\n`));
|
|
305
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
306
|
+
}
|
|
307
|
+
controller.close();
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
return new Response(stream, {
|
|
312
|
+
headers: {
|
|
313
|
+
'Content-Type': 'text/event-stream',
|
|
314
|
+
'Cache-Control': 'no-cache',
|
|
315
|
+
'Connection': 'keep-alive',
|
|
316
|
+
},
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
## Route Configuration
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
// Caching
|
|
325
|
+
export const revalidate = 60; // Revalidate every 60 seconds
|
|
326
|
+
export const dynamic = 'force-dynamic'; // Always dynamic
|
|
327
|
+
|
|
328
|
+
// Runtime
|
|
329
|
+
export const runtime = 'edge'; // or 'nodejs'
|
|
330
|
+
|
|
331
|
+
// Max duration (Vercel)
|
|
332
|
+
export const maxDuration = 30;
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
## Anti-patterns
|
|
336
|
+
|
|
337
|
+
```typescript
|
|
338
|
+
// BAD: Not validating input
|
|
339
|
+
export async function POST(request: Request) {
|
|
340
|
+
const body = await request.json();
|
|
341
|
+
await db.user.create({ data: body }); // SQL injection risk!
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// GOOD: Validate with Zod
|
|
345
|
+
const result = schema.safeParse(body);
|
|
346
|
+
if (!result.success) return errorResponse(result.error);
|
|
347
|
+
|
|
348
|
+
// BAD: Exposing internal errors
|
|
349
|
+
catch (error) {
|
|
350
|
+
return NextResponse.json({ error: error.message }); // Leaks info!
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// GOOD: Generic error message
|
|
354
|
+
catch (error) {
|
|
355
|
+
console.error(error);
|
|
356
|
+
return NextResponse.json({ error: 'Internal error' }, { status: 500 });
|
|
357
|
+
}
|
|
358
|
+
```
|