create-charcole 1.0.0 → 2.0.1
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/.github/workflows/release.yml +26 -0
- package/CHANGELOG.md +25 -0
- package/README.md +11 -1
- package/bin/index.js +94 -49
- package/bin/lib/pkgManager.js +66 -0
- package/bin/lib/templateHandler.js +70 -0
- package/package.json +4 -1
- package/template/js/basePackage.json +28 -0
- package/template/{package-lock.json → js/package-lock.json} +1253 -1253
- package/template/{package.json → js/package.json} +28 -28
- package/template/ts/.env.example +8 -0
- package/template/ts/ARCHITECTURE_DIAGRAMS.md +283 -0
- package/template/ts/CHECKLIST.md +279 -0
- package/template/ts/COMPLETE.md +405 -0
- package/template/ts/ERROR_HANDLING.md +393 -0
- package/template/ts/IMPLEMENTATION.md +368 -0
- package/template/ts/IMPLEMENTATION_COMPLETE.md +363 -0
- package/template/ts/INDEX.md +290 -0
- package/template/ts/QUICK_REFERENCE.md +270 -0
- package/template/ts/README.md +855 -0
- package/template/ts/basePackage.json +36 -0
- package/template/ts/package-lock.json +2428 -0
- package/template/ts/package.json +32 -0
- package/template/ts/src/app.js +75 -0
- package/template/ts/src/app.ts +66 -0
- package/template/ts/src/config/constants.js +20 -0
- package/template/ts/src/config/constants.ts +27 -0
- package/template/ts/src/config/env.js +26 -0
- package/template/ts/src/config/env.ts +40 -0
- package/template/ts/src/middlewares/errorHandler.js +180 -0
- package/template/ts/src/middlewares/errorHandler.ts +209 -0
- package/template/ts/src/middlewares/requestLogger.js +33 -0
- package/template/ts/src/middlewares/requestLogger.ts +38 -0
- package/template/ts/src/middlewares/validateRequest.js +42 -0
- package/template/ts/src/middlewares/validateRequest.ts +46 -0
- package/template/ts/src/modules/health/controller.js +50 -0
- package/template/ts/src/modules/health/controller.ts +64 -0
- package/template/ts/src/routes.js +17 -0
- package/template/ts/src/routes.ts +16 -0
- package/template/ts/src/server.js +38 -0
- package/template/ts/src/server.ts +42 -0
- package/template/ts/src/types/express.d.ts +9 -0
- package/template/ts/src/utils/AppError.js +182 -0
- package/template/ts/src/utils/AppError.ts +220 -0
- package/template/ts/src/utils/logger.js +73 -0
- package/template/ts/src/utils/logger.ts +55 -0
- package/template/ts/src/utils/response.js +51 -0
- package/template/ts/src/utils/response.ts +100 -0
- package/template/ts/test-api.js +100 -0
- package/template/ts/tsconfig.json +19 -0
- /package/template/{.env.example → js/.env.example} +0 -0
- /package/template/{ARCHITECTURE_DIAGRAMS.md → js/ARCHITECTURE_DIAGRAMS.md} +0 -0
- /package/template/{CHECKLIST.md → js/CHECKLIST.md} +0 -0
- /package/template/{COMPLETE.md → js/COMPLETE.md} +0 -0
- /package/template/{ERROR_HANDLING.md → js/ERROR_HANDLING.md} +0 -0
- /package/template/{IMPLEMENTATION.md → js/IMPLEMENTATION.md} +0 -0
- /package/template/{IMPLEMENTATION_COMPLETE.md → js/IMPLEMENTATION_COMPLETE.md} +0 -0
- /package/template/{INDEX.md → js/INDEX.md} +0 -0
- /package/template/{QUICK_REFERENCE.md → js/QUICK_REFERENCE.md} +0 -0
- /package/template/{README.md → js/README.md} +0 -0
- /package/template/{src → js/src}/app.js +0 -0
- /package/template/{src → js/src}/config/constants.js +0 -0
- /package/template/{src → js/src}/config/env.js +0 -0
- /package/template/{src → js/src}/middlewares/errorHandler.js +0 -0
- /package/template/{src → js/src}/middlewares/requestLogger.js +0 -0
- /package/template/{src → js/src}/middlewares/validateRequest.js +0 -0
- /package/template/{src → js/src}/modules/health/controller.js +0 -0
- /package/template/{src → js/src}/routes.js +0 -0
- /package/template/{src → js/src}/server.js +0 -0
- /package/template/{src → js/src}/utils/AppError.js +0 -0
- /package/template/{src → js/src}/utils/logger.js +0 -0
- /package/template/{src → js/src}/utils/response.js +0 -0
- /package/template/{test-api.js → js/test-api.js} +0 -0
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
# Error Handling Guide
|
|
2
|
+
|
|
3
|
+
This document explains the production-level error handling system in Charcole API.
|
|
4
|
+
|
|
5
|
+
## Architecture Overview
|
|
6
|
+
|
|
7
|
+
The error handling system is built on a few key principles:
|
|
8
|
+
|
|
9
|
+
1. **Centralized Error Handling** - All errors flow through one place
|
|
10
|
+
2. **Error Classification** - Distinguish between operational and programmer errors
|
|
11
|
+
3. **Consistent Response Format** - All errors return structured JSON
|
|
12
|
+
4. **Comprehensive Logging** - Full context logged for debugging
|
|
13
|
+
|
|
14
|
+
## Error Types
|
|
15
|
+
|
|
16
|
+
### Operational Errors
|
|
17
|
+
|
|
18
|
+
Expected errors that can be handled gracefully. Client caused or known limitations.
|
|
19
|
+
|
|
20
|
+
```javascript
|
|
21
|
+
import {
|
|
22
|
+
AppError,
|
|
23
|
+
ValidationError,
|
|
24
|
+
AuthenticationError,
|
|
25
|
+
AuthorizationError,
|
|
26
|
+
NotFoundError,
|
|
27
|
+
ConflictError,
|
|
28
|
+
BadRequestError,
|
|
29
|
+
} from "./utils/AppError.js";
|
|
30
|
+
|
|
31
|
+
// Validation error
|
|
32
|
+
throw new ValidationError("Validation failed", [
|
|
33
|
+
{ field: "email", message: "Invalid email", code: "invalid_email" },
|
|
34
|
+
]);
|
|
35
|
+
|
|
36
|
+
// Authentication error
|
|
37
|
+
throw new AuthenticationError("Invalid credentials");
|
|
38
|
+
|
|
39
|
+
// Authorization error
|
|
40
|
+
throw new AuthorizationError("You don't have permission");
|
|
41
|
+
|
|
42
|
+
// Not found error
|
|
43
|
+
throw new NotFoundError("User");
|
|
44
|
+
|
|
45
|
+
// Conflict error (e.g., duplicate email)
|
|
46
|
+
throw new ConflictError("User with this email already exists");
|
|
47
|
+
|
|
48
|
+
// Bad request error
|
|
49
|
+
throw new BadRequestError("Invalid request");
|
|
50
|
+
|
|
51
|
+
// Generic operational error with context
|
|
52
|
+
throw new AppError("Resource quota exceeded", 429, {
|
|
53
|
+
isOperational: true,
|
|
54
|
+
code: "QUOTA_EXCEEDED",
|
|
55
|
+
context: { limit: 100, used: 100 },
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Programmer Errors
|
|
60
|
+
|
|
61
|
+
Unexpected errors that indicate bugs in the code. These are NOT sent to the client in production.
|
|
62
|
+
|
|
63
|
+
- `SyntaxError` - Code has syntax issues
|
|
64
|
+
- `TypeError` - Type mismatch or undefined method
|
|
65
|
+
- `ReferenceError` - Undefined variable
|
|
66
|
+
- `RangeError` - Invalid range
|
|
67
|
+
- Any unhandled error
|
|
68
|
+
|
|
69
|
+
## Using asyncHandler
|
|
70
|
+
|
|
71
|
+
Wrap all async route handlers to catch errors automatically:
|
|
72
|
+
|
|
73
|
+
```javascript
|
|
74
|
+
import { asyncHandler } from "./middlewares/errorHandler.js";
|
|
75
|
+
|
|
76
|
+
// Without asyncHandler - error not caught!
|
|
77
|
+
router.get("/users/:id", (req, res) => {
|
|
78
|
+
const user = await findUser(req.params.id); // Error not caught
|
|
79
|
+
res.json(user);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// With asyncHandler - error caught and passed to error handler
|
|
83
|
+
router.get("/users/:id", asyncHandler(async (req, res) => {
|
|
84
|
+
const user = await findUser(req.params.id); // Error caught!
|
|
85
|
+
res.json(user);
|
|
86
|
+
}));
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Error Handling Flow
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
Request
|
|
93
|
+
↓
|
|
94
|
+
Route Handler (wrapped with asyncHandler)
|
|
95
|
+
↓
|
|
96
|
+
Error thrown ←─ (if something goes wrong)
|
|
97
|
+
↓
|
|
98
|
+
Global Error Handler Middleware
|
|
99
|
+
↓
|
|
100
|
+
Error normalized (AppError, ZodError, TypeError, etc.)
|
|
101
|
+
↓
|
|
102
|
+
Error logged (operational vs programmer)
|
|
103
|
+
↓
|
|
104
|
+
Response sent (sanitized in production)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Logging Behavior
|
|
108
|
+
|
|
109
|
+
### Operational Errors (isOperational: true)
|
|
110
|
+
|
|
111
|
+
Logged as **WARN** with full context:
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
[2024-01-19T10:30:00.000Z] WARN: Operational Error: VALIDATION_ERROR
|
|
115
|
+
{
|
|
116
|
+
"type": "OPERATIONAL",
|
|
117
|
+
"code": "VALIDATION_ERROR",
|
|
118
|
+
"message": "Request validation failed",
|
|
119
|
+
"statusCode": 422,
|
|
120
|
+
"method": "POST",
|
|
121
|
+
"path": "/api/items"
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Programmer Errors (isOperational: false)
|
|
126
|
+
|
|
127
|
+
Logged as **ERROR** with full stack trace:
|
|
128
|
+
|
|
129
|
+
```
|
|
130
|
+
[2024-01-19T10:30:00.000Z] ERROR: Programmer Error: REFERENCE_ERROR
|
|
131
|
+
{
|
|
132
|
+
"type": "PROGRAMMER",
|
|
133
|
+
"code": "REFERENCE_ERROR",
|
|
134
|
+
"message": "user is not defined",
|
|
135
|
+
"statusCode": 500,
|
|
136
|
+
"method": "GET",
|
|
137
|
+
"path": "/api/users/123"
|
|
138
|
+
}
|
|
139
|
+
ReferenceError: user is not defined
|
|
140
|
+
at getUserHandler (/app/src/modules/users/controller.js:15:3)
|
|
141
|
+
...
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Response Format
|
|
145
|
+
|
|
146
|
+
All errors are returned as JSON with this structure:
|
|
147
|
+
|
|
148
|
+
```json
|
|
149
|
+
{
|
|
150
|
+
"success": false,
|
|
151
|
+
"message": "User not found",
|
|
152
|
+
"code": "NOT_FOUND",
|
|
153
|
+
"statusCode": 404,
|
|
154
|
+
"timestamp": "2024-01-19T10:30:00.000Z"
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Validation Errors
|
|
159
|
+
|
|
160
|
+
Include detailed field-level errors:
|
|
161
|
+
|
|
162
|
+
```json
|
|
163
|
+
{
|
|
164
|
+
"success": false,
|
|
165
|
+
"message": "Validation failed",
|
|
166
|
+
"code": "VALIDATION_ERROR",
|
|
167
|
+
"statusCode": 422,
|
|
168
|
+
"errors": [
|
|
169
|
+
{
|
|
170
|
+
"field": "email",
|
|
171
|
+
"message": "Invalid email address",
|
|
172
|
+
"code": "invalid_email"
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
"field": "age",
|
|
176
|
+
"message": "Must be at least 18",
|
|
177
|
+
"code": "too_small"
|
|
178
|
+
}
|
|
179
|
+
],
|
|
180
|
+
"timestamp": "2024-01-19T10:30:00.000Z"
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Production vs Development
|
|
185
|
+
|
|
186
|
+
**In Production:**
|
|
187
|
+
|
|
188
|
+
- Programmer errors return generic message
|
|
189
|
+
- No stack traces sent to client
|
|
190
|
+
- All errors are logged server-side
|
|
191
|
+
|
|
192
|
+
**In Development:**
|
|
193
|
+
|
|
194
|
+
- Full error details returned
|
|
195
|
+
- Stack traces included
|
|
196
|
+
- Detailed context shown
|
|
197
|
+
|
|
198
|
+
## Creating Custom Errors
|
|
199
|
+
|
|
200
|
+
Extend AppError for domain-specific errors:
|
|
201
|
+
|
|
202
|
+
```javascript
|
|
203
|
+
import { AppError } from "./utils/AppError.js";
|
|
204
|
+
|
|
205
|
+
export class UserAlreadyExistsError extends AppError {
|
|
206
|
+
constructor(email) {
|
|
207
|
+
super("User with this email already exists", 409, {
|
|
208
|
+
isOperational: true,
|
|
209
|
+
code: "USER_EXISTS",
|
|
210
|
+
context: { email },
|
|
211
|
+
});
|
|
212
|
+
this.name = "UserAlreadyExistsError";
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Use it:
|
|
217
|
+
throw new UserAlreadyExistsError("john@example.com");
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
## Error Handling Best Practices
|
|
221
|
+
|
|
222
|
+
### ✅ DO
|
|
223
|
+
|
|
224
|
+
```javascript
|
|
225
|
+
// Use asyncHandler for async handlers
|
|
226
|
+
router.get(
|
|
227
|
+
"/users",
|
|
228
|
+
asyncHandler(async (req, res) => {
|
|
229
|
+
const users = await User.find();
|
|
230
|
+
sendSuccess(res, users);
|
|
231
|
+
}),
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
// Throw AppError for operational errors
|
|
235
|
+
if (!user) {
|
|
236
|
+
throw new NotFoundError("User");
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Use proper error codes
|
|
240
|
+
throw new ValidationError("Email is required", [
|
|
241
|
+
{ field: "email", message: "Email is required", code: "required" },
|
|
242
|
+
]);
|
|
243
|
+
|
|
244
|
+
// Log important context
|
|
245
|
+
throw new AppError("Payment failed", 402, {
|
|
246
|
+
isOperational: true,
|
|
247
|
+
code: "PAYMENT_FAILED",
|
|
248
|
+
context: { orderId: "123", amount: 99.99 },
|
|
249
|
+
});
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### ❌ DON'T
|
|
253
|
+
|
|
254
|
+
```javascript
|
|
255
|
+
// Don't use res.status(500).json(...) - use AppError instead
|
|
256
|
+
res.status(500).json({ error: "Something went wrong" });
|
|
257
|
+
|
|
258
|
+
// Don't swallow errors silently
|
|
259
|
+
try {
|
|
260
|
+
await someAsyncWork();
|
|
261
|
+
} catch (error) {
|
|
262
|
+
// WRONG: error is lost!
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Don't forget asyncHandler wrapper
|
|
266
|
+
router.get("/users", async (req, res) => {
|
|
267
|
+
// Error not caught!
|
|
268
|
+
const users = await User.find();
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
// Don't mix error handling styles
|
|
272
|
+
try {
|
|
273
|
+
// ...
|
|
274
|
+
} catch (error) {
|
|
275
|
+
res.status(500).json(error); // Use AppError instead
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Don't log sensitive data
|
|
279
|
+
logger.error("Error", { password: user.password }); // WRONG!
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
## Testing Errors
|
|
283
|
+
|
|
284
|
+
```javascript
|
|
285
|
+
import { AppError, NotFoundError } from "./utils/AppError.js";
|
|
286
|
+
|
|
287
|
+
describe("Error Handling", () => {
|
|
288
|
+
it("should throw NotFoundError", () => {
|
|
289
|
+
expect(() => {
|
|
290
|
+
throw new NotFoundError("User");
|
|
291
|
+
}).toThrow(NotFoundError);
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it("should have correct status code", () => {
|
|
295
|
+
const error = new NotFoundError("User");
|
|
296
|
+
expect(error.statusCode).toBe(404);
|
|
297
|
+
expect(error.isOperational).toBe(true);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it("should include context in error", () => {
|
|
301
|
+
const error = new AppError("Quote limit exceeded", 429, {
|
|
302
|
+
isOperational: true,
|
|
303
|
+
code: "QUOTE_EXCEEDED",
|
|
304
|
+
context: { limit: 100, used: 100 },
|
|
305
|
+
});
|
|
306
|
+
expect(error.context.limit).toBe(100);
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
## Error Codes Reference
|
|
312
|
+
|
|
313
|
+
| Code | Status | Meaning |
|
|
314
|
+
| --------------------- | ------ | --------------------------- |
|
|
315
|
+
| VALIDATION_ERROR | 422 | Request validation failed |
|
|
316
|
+
| NOT_FOUND | 404 | Resource not found |
|
|
317
|
+
| AUTHENTICATION_ERROR | 401 | Invalid credentials |
|
|
318
|
+
| AUTHORIZATION_ERROR | 403 | Permission denied |
|
|
319
|
+
| BAD_REQUEST | 400 | Malformed request |
|
|
320
|
+
| CONFLICT | 409 | Duplicate/conflict resource |
|
|
321
|
+
| INTERNAL_SERVER_ERROR | 500 | Unexpected server error |
|
|
322
|
+
|
|
323
|
+
## Monitoring & Alerts
|
|
324
|
+
|
|
325
|
+
In production, monitor these metrics:
|
|
326
|
+
|
|
327
|
+
1. **Error Rate** - Track operational vs programmer errors
|
|
328
|
+
2. **Response Times** - Identify performance issues
|
|
329
|
+
3. **5xx Errors** - Alert on programmer errors
|
|
330
|
+
4. **Specific Error Codes** - Monitor auth failures, validation errors, etc.
|
|
331
|
+
|
|
332
|
+
## Examples
|
|
333
|
+
|
|
334
|
+
### Example 1: Creating a User with Validation
|
|
335
|
+
|
|
336
|
+
```javascript
|
|
337
|
+
import { asyncHandler, ValidationError } from "./middlewares/errorHandler.js";
|
|
338
|
+
import { sendSuccess } from "./utils/response.js";
|
|
339
|
+
|
|
340
|
+
export const createUser = asyncHandler(async (req, res) => {
|
|
341
|
+
const { email, name } = req.validatedData.body;
|
|
342
|
+
|
|
343
|
+
// Check for duplicate
|
|
344
|
+
const exists = await User.findOne({ email });
|
|
345
|
+
if (exists) {
|
|
346
|
+
throw new ConflictError("User with this email already exists", {
|
|
347
|
+
email,
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Create user
|
|
352
|
+
const user = await User.create({ email, name });
|
|
353
|
+
|
|
354
|
+
// Return success
|
|
355
|
+
sendSuccess(res, user, 201, "User created successfully");
|
|
356
|
+
});
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
### Example 2: Fetching a User with 404 Handling
|
|
360
|
+
|
|
361
|
+
```javascript
|
|
362
|
+
export const getUser = asyncHandler(async (req, res) => {
|
|
363
|
+
const user = await User.findById(req.params.id);
|
|
364
|
+
|
|
365
|
+
if (!user) {
|
|
366
|
+
throw new NotFoundError("User", { id: req.params.id });
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
sendSuccess(res, user);
|
|
370
|
+
});
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
### Example 3: Protected Endpoint with Auth Check
|
|
374
|
+
|
|
375
|
+
```javascript
|
|
376
|
+
export const updateUser = asyncHandler(async (req, res) => {
|
|
377
|
+
const user = await User.findById(req.params.id);
|
|
378
|
+
|
|
379
|
+
if (!user) {
|
|
380
|
+
throw new NotFoundError("User");
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Check authorization
|
|
384
|
+
if (user.id !== req.user.id) {
|
|
385
|
+
throw new AuthorizationError("You can only update your own profile");
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const updated = await user.updateOne(req.validatedData.body);
|
|
389
|
+
sendSuccess(res, updated, 200, "User updated");
|
|
390
|
+
});
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
This comprehensive error handling system ensures that every error in your application is caught, logged appropriately, and returned to the client in a consistent, secure manner.
|