@orchestrator-claude/definitions 3.5.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/agents/api-extractor.md +687 -0
- package/agents/business-rule-miner.md +754 -0
- package/agents/code-archaeologist.md +720 -0
- package/agents/docs-guardian.md +524 -0
- package/agents/implementer.md +512 -0
- package/agents/legacy-discoverer.md +583 -0
- package/agents/legacy-synthesizer.md +1101 -0
- package/agents/orchestrator.md +165 -0
- package/agents/planner.md +365 -0
- package/agents/researcher.md +447 -0
- package/agents/reviewer.md +514 -0
- package/agents/schema-extractor.md +781 -0
- package/agents/specifier.md +360 -0
- package/agents/task-generator.md +390 -0
- package/bin/orch-defs.js +2 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +172 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/DiffCommand.d.ts +13 -0
- package/dist/commands/DiffCommand.d.ts.map +1 -0
- package/dist/commands/DiffCommand.js +74 -0
- package/dist/commands/DiffCommand.js.map +1 -0
- package/dist/commands/SeedCommand.d.ts +19 -0
- package/dist/commands/SeedCommand.d.ts.map +1 -0
- package/dist/commands/SeedCommand.js +56 -0
- package/dist/commands/SeedCommand.js.map +1 -0
- package/dist/http/ApiClient.d.ts +50 -0
- package/dist/http/ApiClient.d.ts.map +1 -0
- package/dist/http/ApiClient.js +58 -0
- package/dist/http/ApiClient.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest/ManifestLoader.d.ts +34 -0
- package/dist/manifest/ManifestLoader.d.ts.map +1 -0
- package/dist/manifest/ManifestLoader.js +110 -0
- package/dist/manifest/ManifestLoader.js.map +1 -0
- package/dist/manifest/types.d.ts +59 -0
- package/dist/manifest/types.d.ts.map +1 -0
- package/dist/manifest/types.js +5 -0
- package/dist/manifest/types.js.map +1 -0
- package/dist/scripts/generate-manifest.d.ts +10 -0
- package/dist/scripts/generate-manifest.d.ts.map +1 -0
- package/dist/scripts/generate-manifest.js +114 -0
- package/dist/scripts/generate-manifest.js.map +1 -0
- package/hooks/post-agent-artifact-relay.sh +157 -0
- package/hooks/post-artifact-generate.sh +39 -0
- package/hooks/post-implement-validate.sh +139 -0
- package/hooks/post-phase-checkpoint.sh +322 -0
- package/hooks/pre-agent-invoke.sh +34 -0
- package/hooks/pre-phase-advance.sh +40 -0
- package/hooks/track-agent-invocation.sh +241 -0
- package/kb/auth-strategies.md +742 -0
- package/kb/docs-constitution.md +310 -0
- package/kb/error-handling.md +555 -0
- package/kb/rest-conventions.md +458 -0
- package/kb/validation-patterns.md +589 -0
- package/manifest.json +314 -0
- package/package.json +65 -0
- package/skills/artifact-validator/SKILL.md +226 -0
- package/skills/docs-guardian/SKILL.md +230 -0
- package/skills/kb-lookup/SKILL.md +257 -0
- package/skills/phase-gate-evaluator/SKILL.md +274 -0
- package/skills/release/SKILL.md +239 -0
- package/skills/release/release.sh +491 -0
- package/skills/smoke-test/SKILL.md +195 -0
- package/skills/workflow-status/SKILL.md +322 -0
- package/workflows/bug-fix.json +74 -0
- package/workflows/feature-development.json +88 -0
- package/workflows/legacy-analysis.json +304 -0
- package/workflows/refactoring.json +74 -0
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Error Handling Patterns"
|
|
3
|
+
category: "patterns"
|
|
4
|
+
tier: "foundational"
|
|
5
|
+
tags: ["api", "errors", "express", "middleware"]
|
|
6
|
+
template: "api"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Error Handling Patterns
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
|
|
13
|
+
This document defines error handling patterns and strategies for building robust, maintainable REST APIs with consistent error responses.
|
|
14
|
+
|
|
15
|
+
**Project**: my-project
|
|
16
|
+
**Template**: api
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## 1. Error Response Format
|
|
21
|
+
|
|
22
|
+
### Standard Error Structure
|
|
23
|
+
|
|
24
|
+
All API errors MUST follow this consistent format:
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
interface ErrorResponse {
|
|
28
|
+
error: string; // Error type/name
|
|
29
|
+
message: string; // Human-readable message
|
|
30
|
+
details?: unknown; // Optional additional context
|
|
31
|
+
timestamp: string; // ISO 8601 timestamp
|
|
32
|
+
path: string; // Request path
|
|
33
|
+
requestId?: string; // Request ID for tracing
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Example
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"error": "ValidationError",
|
|
42
|
+
"message": "Invalid email format",
|
|
43
|
+
"details": {
|
|
44
|
+
"field": "email",
|
|
45
|
+
"value": "invalid-email",
|
|
46
|
+
"constraint": "Must be a valid email address"
|
|
47
|
+
},
|
|
48
|
+
"timestamp": "2025-01-06T12:00:00Z",
|
|
49
|
+
"path": "/api/v1/users",
|
|
50
|
+
"requestId": "550e8400-e29b-41d4-a716-446655440000"
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## 2. Error Types
|
|
57
|
+
|
|
58
|
+
### Domain Errors
|
|
59
|
+
|
|
60
|
+
Errors from business logic (domain layer).
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
// src/domain/errors/UserNotFoundError.ts
|
|
64
|
+
export class UserNotFoundError extends Error {
|
|
65
|
+
constructor(userId: string) {
|
|
66
|
+
super(`User not found: ${userId}`);
|
|
67
|
+
this.name = 'UserNotFoundError';
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// src/domain/errors/DuplicateEmailError.ts
|
|
72
|
+
export class DuplicateEmailError extends Error {
|
|
73
|
+
constructor(email: string) {
|
|
74
|
+
super(`Email already exists: ${email}`);
|
|
75
|
+
this.name = 'DuplicateEmailError';
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Validation Errors
|
|
81
|
+
|
|
82
|
+
Errors from input validation (using Zod).
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
import { z } from 'zod';
|
|
86
|
+
|
|
87
|
+
const CreateUserSchema = z.object({
|
|
88
|
+
name: z.string().min(1).max(100),
|
|
89
|
+
email: z.string().email(),
|
|
90
|
+
age: z.number().int().positive().optional(),
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Validation error example
|
|
94
|
+
const result = CreateUserSchema.safeParse(input);
|
|
95
|
+
if (!result.success) {
|
|
96
|
+
// result.error.issues contains detailed validation errors
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Infrastructure Errors
|
|
101
|
+
|
|
102
|
+
Errors from external systems (database, APIs).
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
export class DatabaseError extends Error {
|
|
106
|
+
constructor(message: string, public readonly cause?: Error) {
|
|
107
|
+
super(message);
|
|
108
|
+
this.name = 'DatabaseError';
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export class ExternalAPIError extends Error {
|
|
113
|
+
constructor(
|
|
114
|
+
service: string,
|
|
115
|
+
message: string,
|
|
116
|
+
public readonly statusCode?: number
|
|
117
|
+
) {
|
|
118
|
+
super(`${service}: ${message}`);
|
|
119
|
+
this.name = 'ExternalAPIError';
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
---
|
|
125
|
+
|
|
126
|
+
## 3. Error Middleware
|
|
127
|
+
|
|
128
|
+
### Global Error Handler
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
// src/presentation/middleware/errorHandler.ts
|
|
132
|
+
import type { Request, Response, NextFunction } from 'express';
|
|
133
|
+
|
|
134
|
+
export function errorHandler(
|
|
135
|
+
err: Error,
|
|
136
|
+
req: Request,
|
|
137
|
+
res: Response,
|
|
138
|
+
next: NextFunction
|
|
139
|
+
): void {
|
|
140
|
+
// Log error
|
|
141
|
+
console.error('API Error:', {
|
|
142
|
+
error: err.name,
|
|
143
|
+
message: err.message,
|
|
144
|
+
stack: process.env.NODE_ENV === 'development' ? err.stack : undefined,
|
|
145
|
+
path: req.path,
|
|
146
|
+
method: req.method,
|
|
147
|
+
requestId: req.id,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// Map error to HTTP status
|
|
151
|
+
const statusCode = getStatusCode(err);
|
|
152
|
+
const errorResponse = createErrorResponse(err, req);
|
|
153
|
+
|
|
154
|
+
res.status(statusCode).json(errorResponse);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function getStatusCode(err: Error): number {
|
|
158
|
+
// Domain errors
|
|
159
|
+
if (err.name === 'UserNotFoundError') return 404;
|
|
160
|
+
if (err.name === 'DuplicateEmailError') return 409;
|
|
161
|
+
if (err.name === 'UnauthorizedError') return 401;
|
|
162
|
+
if (err.name === 'ForbiddenError') return 403;
|
|
163
|
+
|
|
164
|
+
// Validation errors
|
|
165
|
+
if (err.name === 'ZodError') return 400;
|
|
166
|
+
|
|
167
|
+
// Default to 500
|
|
168
|
+
return 500;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function createErrorResponse(err: Error, req: Request): ErrorResponse {
|
|
172
|
+
const isProduction = process.env.NODE_ENV === 'production';
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
error: err.name,
|
|
176
|
+
message: isProduction && err.name === 'Error'
|
|
177
|
+
? 'An unexpected error occurred'
|
|
178
|
+
: err.message,
|
|
179
|
+
details: getErrorDetails(err),
|
|
180
|
+
timestamp: new Date().toISOString(),
|
|
181
|
+
path: req.path,
|
|
182
|
+
requestId: req.id,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function getErrorDetails(err: Error): unknown {
|
|
187
|
+
// For Zod validation errors
|
|
188
|
+
if (err.name === 'ZodError') {
|
|
189
|
+
const zodError = err as z.ZodError;
|
|
190
|
+
return zodError.issues.map(issue => ({
|
|
191
|
+
path: issue.path.join('.'),
|
|
192
|
+
message: issue.message,
|
|
193
|
+
code: issue.code,
|
|
194
|
+
}));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// For other errors, return nothing in production
|
|
198
|
+
if (process.env.NODE_ENV === 'production') {
|
|
199
|
+
return undefined;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return { stack: err.stack };
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Register Error Handler
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
// src/server.ts
|
|
210
|
+
import express from 'express';
|
|
211
|
+
import { errorHandler } from './presentation/middleware/errorHandler';
|
|
212
|
+
|
|
213
|
+
const app = express();
|
|
214
|
+
|
|
215
|
+
// ... routes ...
|
|
216
|
+
|
|
217
|
+
// Error handler MUST be last middleware
|
|
218
|
+
app.use(errorHandler);
|
|
219
|
+
|
|
220
|
+
export default app;
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## 4. Validation Error Handling
|
|
226
|
+
|
|
227
|
+
### Request Validation Middleware
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
// src/presentation/middleware/validateRequest.ts
|
|
231
|
+
import type { Request, Response, NextFunction } from 'express';
|
|
232
|
+
import type { z } from 'zod';
|
|
233
|
+
|
|
234
|
+
export function validateRequest<T extends z.ZodSchema>(schema: T) {
|
|
235
|
+
return (req: Request, res: Response, next: NextFunction) => {
|
|
236
|
+
const result = schema.safeParse(req.body);
|
|
237
|
+
|
|
238
|
+
if (!result.success) {
|
|
239
|
+
const errorResponse: ErrorResponse = {
|
|
240
|
+
error: 'ValidationError',
|
|
241
|
+
message: 'Request validation failed',
|
|
242
|
+
details: result.error.issues.map(issue => ({
|
|
243
|
+
path: issue.path.join('.'),
|
|
244
|
+
message: issue.message,
|
|
245
|
+
code: issue.code,
|
|
246
|
+
})),
|
|
247
|
+
timestamp: new Date().toISOString(),
|
|
248
|
+
path: req.path,
|
|
249
|
+
requestId: req.id,
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
return res.status(400).json(errorResponse);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Attach validated data to request
|
|
256
|
+
req.body = result.data;
|
|
257
|
+
next();
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Usage in Routes
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
import { validateRequest } from '../middleware/validateRequest';
|
|
266
|
+
import { CreateUserSchema } from '../schemas/user';
|
|
267
|
+
|
|
268
|
+
router.post(
|
|
269
|
+
'/api/v1/users',
|
|
270
|
+
validateRequest(CreateUserSchema),
|
|
271
|
+
async (req, res, next) => {
|
|
272
|
+
try {
|
|
273
|
+
// req.body is now type-safe and validated
|
|
274
|
+
const result = await createUserUseCase.execute(req.body);
|
|
275
|
+
|
|
276
|
+
if (result.isErr()) {
|
|
277
|
+
// Convert domain error to HTTP error
|
|
278
|
+
return next(new DuplicateEmailError(req.body.email));
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
res.status(201).json(result.value);
|
|
282
|
+
} catch (err) {
|
|
283
|
+
next(err); // Pass to error handler
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
);
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## 5. Async Error Handling
|
|
292
|
+
|
|
293
|
+
### Async Route Handler Wrapper
|
|
294
|
+
|
|
295
|
+
```typescript
|
|
296
|
+
// src/presentation/middleware/asyncHandler.ts
|
|
297
|
+
import type { Request, Response, NextFunction } from 'express';
|
|
298
|
+
|
|
299
|
+
type AsyncRouteHandler = (
|
|
300
|
+
req: Request,
|
|
301
|
+
res: Response,
|
|
302
|
+
next: NextFunction
|
|
303
|
+
) => Promise<void>;
|
|
304
|
+
|
|
305
|
+
export function asyncHandler(fn: AsyncRouteHandler) {
|
|
306
|
+
return (req: Request, res: Response, next: NextFunction) => {
|
|
307
|
+
Promise.resolve(fn(req, res, next)).catch(next);
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### Usage
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
import { asyncHandler } from '../middleware/asyncHandler';
|
|
316
|
+
|
|
317
|
+
router.get(
|
|
318
|
+
'/api/v1/users/:id',
|
|
319
|
+
asyncHandler(async (req, res) => {
|
|
320
|
+
const result = await getUserUseCase.execute({ userId: req.params.id });
|
|
321
|
+
|
|
322
|
+
if (result.isErr()) {
|
|
323
|
+
throw new UserNotFoundError(req.params.id);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
res.json(result.value);
|
|
327
|
+
})
|
|
328
|
+
);
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
## 6. Result Pattern
|
|
334
|
+
|
|
335
|
+
### Use Result<T, E> for Use Cases
|
|
336
|
+
|
|
337
|
+
```typescript
|
|
338
|
+
// src/shared/types/Result.ts
|
|
339
|
+
export type Result<T, E> =
|
|
340
|
+
| { isOk: true; value: T }
|
|
341
|
+
| { isOk: false; error: E };
|
|
342
|
+
|
|
343
|
+
export function ok<T>(value: T): Result<T, never> {
|
|
344
|
+
return { isOk: true, value };
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export function err<E>(error: E): Result<never, E> {
|
|
348
|
+
return { isOk: false, error };
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Usage in Use Case
|
|
352
|
+
export class GetUserUseCase {
|
|
353
|
+
async execute(input: { userId: string }): Promise<Result<User, string>> {
|
|
354
|
+
const user = await this.userRepository.findById(input.userId);
|
|
355
|
+
|
|
356
|
+
if (!user) {
|
|
357
|
+
return err('User not found');
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return ok(user);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Usage in Route
|
|
365
|
+
router.get('/api/v1/users/:id', async (req, res, next) => {
|
|
366
|
+
const result = await getUserUseCase.execute({ userId: req.params.id });
|
|
367
|
+
|
|
368
|
+
if (!result.isOk) {
|
|
369
|
+
return next(new UserNotFoundError(req.params.id));
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
res.json(result.value);
|
|
373
|
+
});
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
## 7. Not Found Handler
|
|
379
|
+
|
|
380
|
+
### 404 Handler
|
|
381
|
+
|
|
382
|
+
```typescript
|
|
383
|
+
// src/presentation/middleware/notFoundHandler.ts
|
|
384
|
+
import type { Request, Response } from 'express';
|
|
385
|
+
|
|
386
|
+
export function notFoundHandler(req: Request, res: Response): void {
|
|
387
|
+
const errorResponse: ErrorResponse = {
|
|
388
|
+
error: 'NotFound',
|
|
389
|
+
message: `Route not found: ${req.method} ${req.path}`,
|
|
390
|
+
timestamp: new Date().toISOString(),
|
|
391
|
+
path: req.path,
|
|
392
|
+
requestId: req.id,
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
res.status(404).json(errorResponse);
|
|
396
|
+
}
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
### Register Before Error Handler
|
|
400
|
+
|
|
401
|
+
```typescript
|
|
402
|
+
// src/server.ts
|
|
403
|
+
import { notFoundHandler } from './presentation/middleware/notFoundHandler';
|
|
404
|
+
import { errorHandler } from './presentation/middleware/errorHandler';
|
|
405
|
+
|
|
406
|
+
// ... routes ...
|
|
407
|
+
|
|
408
|
+
// 404 handler
|
|
409
|
+
app.use(notFoundHandler);
|
|
410
|
+
|
|
411
|
+
// Error handler
|
|
412
|
+
app.use(errorHandler);
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
---
|
|
416
|
+
|
|
417
|
+
## 8. Error Logging
|
|
418
|
+
|
|
419
|
+
### Structured Logging
|
|
420
|
+
|
|
421
|
+
```typescript
|
|
422
|
+
interface ErrorLog {
|
|
423
|
+
level: 'error';
|
|
424
|
+
error: string;
|
|
425
|
+
message: string;
|
|
426
|
+
stack?: string;
|
|
427
|
+
context: {
|
|
428
|
+
requestId?: string;
|
|
429
|
+
path: string;
|
|
430
|
+
method: string;
|
|
431
|
+
userId?: string;
|
|
432
|
+
};
|
|
433
|
+
timestamp: string;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function logError(err: Error, req: Request): void {
|
|
437
|
+
const log: ErrorLog = {
|
|
438
|
+
level: 'error',
|
|
439
|
+
error: err.name,
|
|
440
|
+
message: err.message,
|
|
441
|
+
stack: err.stack,
|
|
442
|
+
context: {
|
|
443
|
+
requestId: req.id,
|
|
444
|
+
path: req.path,
|
|
445
|
+
method: req.method,
|
|
446
|
+
userId: req.user?.id, // If authenticated
|
|
447
|
+
},
|
|
448
|
+
timestamp: new Date().toISOString(),
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
// Log as JSON for parsing by log aggregators
|
|
452
|
+
console.error(JSON.stringify(log));
|
|
453
|
+
}
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
---
|
|
457
|
+
|
|
458
|
+
## 9. Rate Limit Errors
|
|
459
|
+
|
|
460
|
+
### Rate Limit Handler
|
|
461
|
+
|
|
462
|
+
```typescript
|
|
463
|
+
import rateLimit from 'express-rate-limit';
|
|
464
|
+
|
|
465
|
+
const limiter = rateLimit({
|
|
466
|
+
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
467
|
+
max: 100,
|
|
468
|
+
handler: (req, res) => {
|
|
469
|
+
const errorResponse: ErrorResponse = {
|
|
470
|
+
error: 'TooManyRequests',
|
|
471
|
+
message: 'Too many requests from this IP, please try again later',
|
|
472
|
+
timestamp: new Date().toISOString(),
|
|
473
|
+
path: req.path,
|
|
474
|
+
requestId: req.id,
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
res.status(429).json(errorResponse);
|
|
478
|
+
},
|
|
479
|
+
standardHeaders: true,
|
|
480
|
+
legacyHeaders: false,
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
app.use('/api/', limiter);
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
---
|
|
487
|
+
|
|
488
|
+
## 10. Testing Error Handling
|
|
489
|
+
|
|
490
|
+
### Test Error Responses
|
|
491
|
+
|
|
492
|
+
```typescript
|
|
493
|
+
import request from 'supertest';
|
|
494
|
+
import { app } from '../src/server';
|
|
495
|
+
|
|
496
|
+
describe('Error Handling', () => {
|
|
497
|
+
it('should return 404 for non-existent route', async () => {
|
|
498
|
+
const response = await request(app)
|
|
499
|
+
.get('/api/v1/nonexistent')
|
|
500
|
+
.expect(404);
|
|
501
|
+
|
|
502
|
+
expect(response.body).toHaveProperty('error', 'NotFound');
|
|
503
|
+
expect(response.body).toHaveProperty('message');
|
|
504
|
+
expect(response.body).toHaveProperty('timestamp');
|
|
505
|
+
expect(response.body).toHaveProperty('path', '/api/v1/nonexistent');
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
it('should return 400 for validation errors', async () => {
|
|
509
|
+
const response = await request(app)
|
|
510
|
+
.post('/api/v1/users')
|
|
511
|
+
.send({ name: '', email: 'invalid' })
|
|
512
|
+
.expect(400);
|
|
513
|
+
|
|
514
|
+
expect(response.body.error).toBe('ValidationError');
|
|
515
|
+
expect(response.body.details).toBeInstanceOf(Array);
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
it('should return 500 for unexpected errors', async () => {
|
|
519
|
+
// Mock repository to throw error
|
|
520
|
+
jest.spyOn(userRepository, 'findById').mockRejectedValue(new Error('DB Error'));
|
|
521
|
+
|
|
522
|
+
const response = await request(app)
|
|
523
|
+
.get('/api/v1/users/123')
|
|
524
|
+
.expect(500);
|
|
525
|
+
|
|
526
|
+
expect(response.body.error).toBe('Error');
|
|
527
|
+
expect(response.body.message).toBeTruthy();
|
|
528
|
+
});
|
|
529
|
+
});
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
---
|
|
533
|
+
|
|
534
|
+
## Best Practices
|
|
535
|
+
|
|
536
|
+
1. **Always use middleware for error handling** - Don't handle errors in routes
|
|
537
|
+
2. **Log all errors** - Include request context for debugging
|
|
538
|
+
3. **Use Result pattern** - Avoid throwing errors in use cases
|
|
539
|
+
4. **Consistent error format** - All errors follow same structure
|
|
540
|
+
5. **Sanitize in production** - Don't expose stack traces or sensitive data
|
|
541
|
+
6. **Test error scenarios** - Test all error cases (4xx, 5xx)
|
|
542
|
+
|
|
543
|
+
---
|
|
544
|
+
|
|
545
|
+
## References
|
|
546
|
+
|
|
547
|
+
- [Express Error Handling](https://expressjs.com/en/guide/error-handling.html)
|
|
548
|
+
- [HTTP Status Codes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status)
|
|
549
|
+
- [Zod Validation](https://zod.dev/)
|
|
550
|
+
|
|
551
|
+
---
|
|
552
|
+
|
|
553
|
+
**Generated for**: my-project
|
|
554
|
+
**Template**: api
|
|
555
|
+
**Constitution Preset**: orchestrator
|