create-charcole 2.0.4 → 2.1.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/CHANGELOG.md +200 -14
- package/README.md +137 -332
- package/bin/index.js +236 -55
- package/bin/lib/pkgManager.js +8 -25
- package/bin/lib/templateHandler.js +5 -42
- package/package.json +2 -2
- package/plans/V2_1_PLAN.md +20 -0
- package/template/js/.env.example +8 -0
- package/template/js/basePackage.json +11 -13
- package/template/js/src/app.js +4 -1
- package/template/js/src/modules/auth/auth.constants.js +3 -0
- package/template/js/src/modules/auth/auth.controller.js +29 -0
- package/template/js/src/modules/auth/auth.middlewares.js +19 -0
- package/template/js/src/modules/auth/auth.routes.js +9 -0
- package/template/js/src/modules/auth/auth.schemas.js +60 -0
- package/template/js/src/modules/auth/auth.service.js +67 -0
- package/template/js/src/modules/auth/package.json +6 -0
- package/template/js/src/repositories/user.repo.js +19 -0
- package/template/js/src/routes/index.js +25 -0
- package/template/js/src/routes/protected.js +13 -0
- package/template/ts/.env.example +8 -0
- package/template/ts/basePackage.json +19 -15
- package/template/ts/build.js +46 -0
- package/template/ts/src/app.ts +5 -4
- package/template/ts/src/middlewares/errorHandler.ts +15 -23
- package/template/ts/src/middlewares/requestLogger.ts +1 -1
- package/template/ts/src/middlewares/validateRequest.ts +1 -1
- package/template/ts/src/modules/auth/auth.constants.ts +6 -0
- package/template/ts/src/modules/auth/auth.controller.ts +32 -0
- package/template/ts/src/modules/auth/auth.middlewares.ts +46 -0
- package/template/ts/src/modules/auth/auth.routes.ts +9 -0
- package/template/ts/src/modules/auth/auth.schemas.ts +73 -0
- package/template/ts/src/modules/auth/auth.service.ts +106 -0
- package/template/ts/src/modules/auth/package.json +10 -0
- package/template/ts/src/modules/health/controller.ts +3 -3
- package/template/ts/src/repositories/user.repo.ts +33 -0
- package/template/ts/src/routes/index.ts +24 -0
- package/template/ts/src/routes/protected.ts +13 -0
- package/template/ts/src/server.ts +0 -1
- package/template/ts/src/utils/logger.ts +1 -1
- package/template/ts/tsconfig.json +14 -7
- package/template/js/ARCHITECTURE_DIAGRAMS.md +0 -283
- package/template/js/CHECKLIST.md +0 -279
- package/template/js/COMPLETE.md +0 -405
- package/template/js/ERROR_HANDLING.md +0 -393
- package/template/js/IMPLEMENTATION.md +0 -368
- package/template/js/IMPLEMENTATION_COMPLETE.md +0 -363
- package/template/js/INDEX.md +0 -290
- package/template/js/QUICK_REFERENCE.md +0 -270
- package/template/js/package.json +0 -28
- package/template/js/src/routes.js +0 -17
- package/template/js/test-api.js +0 -100
- package/template/ts/ARCHITECTURE_DIAGRAMS.md +0 -283
- package/template/ts/CHECKLIST.md +0 -279
- package/template/ts/COMPLETE.md +0 -405
- package/template/ts/ERROR_HANDLING.md +0 -393
- package/template/ts/IMPLEMENTATION.md +0 -368
- package/template/ts/IMPLEMENTATION_COMPLETE.md +0 -363
- package/template/ts/INDEX.md +0 -290
- package/template/ts/QUICK_REFERENCE.md +0 -270
- package/template/ts/package.json +0 -32
- package/template/ts/src/app.js +0 -75
- package/template/ts/src/config/constants.js +0 -20
- package/template/ts/src/config/env.js +0 -26
- package/template/ts/src/middlewares/errorHandler.js +0 -180
- package/template/ts/src/middlewares/requestLogger.js +0 -33
- package/template/ts/src/middlewares/validateRequest.js +0 -42
- package/template/ts/src/modules/health/controller.js +0 -50
- package/template/ts/src/routes.js +0 -17
- package/template/ts/src/routes.ts +0 -16
- package/template/ts/src/server.js +0 -38
- package/template/ts/src/utils/AppError.js +0 -182
- package/template/ts/src/utils/logger.js +0 -73
- package/template/ts/src/utils/response.js +0 -51
- package/template/ts/test-api.js +0 -100
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
# Production-Level Error Handling System - Quick Reference
|
|
2
|
-
|
|
3
|
-
## 🎯 Problem Solved
|
|
4
|
-
|
|
5
|
-
❌ **Before:** Random `res.status(500).json(...)` scattered throughout code
|
|
6
|
-
✅ **After:** Single centralized error handler with distinguished error types
|
|
7
|
-
|
|
8
|
-
## 🏗️ System Architecture
|
|
9
|
-
|
|
10
|
-
```
|
|
11
|
-
Every Request
|
|
12
|
-
↓
|
|
13
|
-
Route Handler (wrapped with asyncHandler)
|
|
14
|
-
↓
|
|
15
|
-
├─→ Success? → sendSuccess(res, data)
|
|
16
|
-
│
|
|
17
|
-
└─→ Error thrown ✘
|
|
18
|
-
↓
|
|
19
|
-
Global Error Handler
|
|
20
|
-
↓
|
|
21
|
-
Normalize Error
|
|
22
|
-
├─→ Is it AppError? Use it
|
|
23
|
-
├─→ Is it ZodError? Convert to ValidationError
|
|
24
|
-
├─→ Is it TypeError/ReferenceError/etc? Convert to InternalServerError
|
|
25
|
-
↓
|
|
26
|
-
Classify Error
|
|
27
|
-
├─→ Operational? (isOperational: true)
|
|
28
|
-
│ ├─ Log as WARN with context
|
|
29
|
-
│ └─ Send full details to client
|
|
30
|
-
│
|
|
31
|
-
└─→ Programmer Error? (isOperational: false)
|
|
32
|
-
├─ Log as ERROR with stack trace
|
|
33
|
-
└─ Send generic message in production
|
|
34
|
-
↓
|
|
35
|
-
Send Consistent JSON Response
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
## 📦 Error Classes (Use These!)
|
|
39
|
-
|
|
40
|
-
```javascript
|
|
41
|
-
import {
|
|
42
|
-
AppError, // Base class
|
|
43
|
-
ValidationError, // Input validation failed
|
|
44
|
-
AuthenticationError, // Auth failed (401)
|
|
45
|
-
AuthorizationError, // Permission denied (403)
|
|
46
|
-
NotFoundError, // Resource not found (404)
|
|
47
|
-
ConflictError, // Duplicate/conflict (409)
|
|
48
|
-
BadRequestError, // Malformed request (400)
|
|
49
|
-
InternalServerError, // Unexpected error (500)
|
|
50
|
-
} from "./middlewares/errorHandler.js";
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
## 🎬 Quick Start
|
|
54
|
-
|
|
55
|
-
### 1. Throw Operational Errors
|
|
56
|
-
|
|
57
|
-
```javascript
|
|
58
|
-
// Validation
|
|
59
|
-
throw new ValidationError("Invalid input", [
|
|
60
|
-
{ field: "email", message: "Invalid email", code: "invalid_email" },
|
|
61
|
-
]);
|
|
62
|
-
|
|
63
|
-
// Not found
|
|
64
|
-
throw new NotFoundError("User", { id: userId });
|
|
65
|
-
|
|
66
|
-
// Duplicate
|
|
67
|
-
throw new ConflictError("Email already exists", { email });
|
|
68
|
-
|
|
69
|
-
// Auth
|
|
70
|
-
throw new AuthenticationError("Invalid credentials");
|
|
71
|
-
|
|
72
|
-
// Permission
|
|
73
|
-
throw new AuthorizationError("Access denied");
|
|
74
|
-
|
|
75
|
-
// Generic operational error
|
|
76
|
-
throw new AppError("Request failed", 400, {
|
|
77
|
-
isOperational: true,
|
|
78
|
-
code: "CUSTOM_ERROR",
|
|
79
|
-
context: { details: "..." },
|
|
80
|
-
});
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
### 2. Wrap All Async Handlers
|
|
84
|
-
|
|
85
|
-
```javascript
|
|
86
|
-
import { asyncHandler } from "./middlewares/errorHandler.js";
|
|
87
|
-
|
|
88
|
-
// ✅ Correct
|
|
89
|
-
router.post(
|
|
90
|
-
"/users",
|
|
91
|
-
asyncHandler(async (req, res) => {
|
|
92
|
-
const user = await User.create(req.body);
|
|
93
|
-
sendSuccess(res, user, 201);
|
|
94
|
-
}),
|
|
95
|
-
);
|
|
96
|
-
|
|
97
|
-
// ❌ Wrong - errors leak!
|
|
98
|
-
router.post("/users", async (req, res) => {
|
|
99
|
-
const user = await User.create(req.body); // Error not caught!
|
|
100
|
-
sendSuccess(res, user, 201);
|
|
101
|
-
});
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
### 3. Use Validation Middleware
|
|
105
|
-
|
|
106
|
-
```javascript
|
|
107
|
-
import { validateRequest } from "./middlewares/validateRequest.js";
|
|
108
|
-
import { z } from "zod";
|
|
109
|
-
|
|
110
|
-
const createUserSchema = z.object({
|
|
111
|
-
body: z.object({
|
|
112
|
-
email: z.string().email("Invalid email"),
|
|
113
|
-
name: z.string().min(1, "Name required"),
|
|
114
|
-
}),
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
router.post(
|
|
118
|
-
"/users",
|
|
119
|
-
validateRequest(createUserSchema),
|
|
120
|
-
asyncHandler(async (req, res) => {
|
|
121
|
-
// req.validatedData.body has been validated
|
|
122
|
-
const user = await User.create(req.validatedData.body);
|
|
123
|
-
sendSuccess(res, user, 201, "User created");
|
|
124
|
-
}),
|
|
125
|
-
);
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
## 📊 Logging Behavior
|
|
129
|
-
|
|
130
|
-
| Error Type | Level | Includes Stack | Info Sent to Client |
|
|
131
|
-
| ---------------------- | ----- | -------------- | ---------------------- |
|
|
132
|
-
| ValidationError | WARN | ❌ | ✅ All details |
|
|
133
|
-
| NotFoundError | WARN | ❌ | ✅ All details |
|
|
134
|
-
| AuthenticationError | WARN | ❌ | ✅ All details |
|
|
135
|
-
| TypeError (programmer) | ERROR | ✅ | ❌ Generic only (prod) |
|
|
136
|
-
| Unhandled (programmer) | FATAL | ✅ | ❌ Generic only (prod) |
|
|
137
|
-
|
|
138
|
-
## 📋 Response Format
|
|
139
|
-
|
|
140
|
-
All responses are consistent JSON:
|
|
141
|
-
|
|
142
|
-
### Success
|
|
143
|
-
|
|
144
|
-
```json
|
|
145
|
-
{
|
|
146
|
-
"success": true,
|
|
147
|
-
"message": "User created successfully",
|
|
148
|
-
"data": { "id": "123", "name": "John" },
|
|
149
|
-
"timestamp": "2024-01-19T15:55:30.000Z"
|
|
150
|
-
}
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
### Operational Error
|
|
154
|
-
|
|
155
|
-
```json
|
|
156
|
-
{
|
|
157
|
-
"success": false,
|
|
158
|
-
"message": "User not found",
|
|
159
|
-
"code": "NOT_FOUND",
|
|
160
|
-
"statusCode": 404,
|
|
161
|
-
"context": { "id": "999" },
|
|
162
|
-
"timestamp": "2024-01-19T15:55:30.000Z"
|
|
163
|
-
}
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
### Validation Error
|
|
167
|
-
|
|
168
|
-
```json
|
|
169
|
-
{
|
|
170
|
-
"success": false,
|
|
171
|
-
"message": "Validation failed",
|
|
172
|
-
"code": "VALIDATION_ERROR",
|
|
173
|
-
"statusCode": 422,
|
|
174
|
-
"errors": [
|
|
175
|
-
{ "field": "email", "message": "Invalid email", "code": "invalid_email" }
|
|
176
|
-
],
|
|
177
|
-
"timestamp": "2024-01-19T15:55:30.000Z"
|
|
178
|
-
}
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
### Programmer Error (Production)
|
|
182
|
-
|
|
183
|
-
```json
|
|
184
|
-
{
|
|
185
|
-
"success": false,
|
|
186
|
-
"message": "Internal server error",
|
|
187
|
-
"code": "INTERNAL_SERVER_ERROR",
|
|
188
|
-
"timestamp": "2024-01-19T15:55:30.000Z"
|
|
189
|
-
}
|
|
190
|
-
```
|
|
191
|
-
|
|
192
|
-
## 🛡️ Golden Rules
|
|
193
|
-
|
|
194
|
-
1. **Always use asyncHandler** for async route handlers
|
|
195
|
-
|
|
196
|
-
```javascript
|
|
197
|
-
router.get("/endpoint", asyncHandler(async (req, res) => { ... }))
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
2. **Never use res.status().json()** for errors - throw AppError instead
|
|
201
|
-
|
|
202
|
-
```javascript
|
|
203
|
-
// ❌ Wrong
|
|
204
|
-
res.status(400).json({ error: "..." });
|
|
205
|
-
|
|
206
|
-
// ✅ Correct
|
|
207
|
-
throw new BadRequestError("Invalid input");
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
3. **Validate request early** with validateRequest middleware
|
|
211
|
-
|
|
212
|
-
```javascript
|
|
213
|
-
router.post("/endpoint", validateRequest(schema), handler);
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
4. **Throw, don't catch** - let global handler catch
|
|
217
|
-
|
|
218
|
-
```javascript
|
|
219
|
-
// ❌ Wrong
|
|
220
|
-
try {
|
|
221
|
-
await someTask();
|
|
222
|
-
} catch (err) {
|
|
223
|
-
res.status(500).json(err);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// ✅ Correct
|
|
227
|
-
await someTask(); // Error bubbles up to global handler
|
|
228
|
-
```
|
|
229
|
-
|
|
230
|
-
5. **Include context** when throwing errors
|
|
231
|
-
```javascript
|
|
232
|
-
throw new NotFoundError("User", { id: userId, email: userEmail });
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
## 🚀 Features at a Glance
|
|
236
|
-
|
|
237
|
-
| Feature | Status |
|
|
238
|
-
| ------------------------------------------------ | ------ |
|
|
239
|
-
| Centralized error handler | ✅ |
|
|
240
|
-
| Error classification (operational vs programmer) | ✅ |
|
|
241
|
-
| Validation error formatting | ✅ |
|
|
242
|
-
| Async error wrapping | ✅ |
|
|
243
|
-
| Stack trace logging | ✅ |
|
|
244
|
-
| Production sanitization | ✅ |
|
|
245
|
-
| Request logging | ✅ |
|
|
246
|
-
| Graceful shutdown | ✅ |
|
|
247
|
-
| Unhandled exception catching | ✅ |
|
|
248
|
-
| Consistent JSON responses | ✅ |
|
|
249
|
-
|
|
250
|
-
## 📚 Full Documentation
|
|
251
|
-
|
|
252
|
-
- **[ERROR_HANDLING.md](ERROR_HANDLING.md)** - Comprehensive guide with examples
|
|
253
|
-
- **[IMPLEMENTATION_COMPLETE.md](IMPLEMENTATION_COMPLETE.md)** - Full implementation details
|
|
254
|
-
|
|
255
|
-
## 🎓 Key Files
|
|
256
|
-
|
|
257
|
-
| File | Purpose |
|
|
258
|
-
| ------------------------------------ | ----------------------------------- |
|
|
259
|
-
| `src/utils/AppError.js` | Error class hierarchy |
|
|
260
|
-
| `src/middlewares/errorHandler.js` | Global error handler + asyncHandler |
|
|
261
|
-
| `src/middlewares/validateRequest.js` | Zod validation middleware |
|
|
262
|
-
| `src/middlewares/requestLogger.js` | Request logging |
|
|
263
|
-
| `src/utils/logger.js` | Structured logging with colors |
|
|
264
|
-
| `src/utils/response.js` | Success response helpers |
|
|
265
|
-
|
|
266
|
-
---
|
|
267
|
-
|
|
268
|
-
**That's it! Your API now has enterprise-grade error handling.** 🎯
|
|
269
|
-
|
|
270
|
-
Every error flows through one place. Every response is consistent. Errors are properly classified and logged. You're ready for production. 🚀
|
package/template/ts/package.json
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "charcole",
|
|
3
|
-
"version": "2.0.4",
|
|
4
|
-
"description": "Production-grade Node.js Express API",
|
|
5
|
-
"main": "src/server.js",
|
|
6
|
-
"scripts": {
|
|
7
|
-
"start": "node src/server.js",
|
|
8
|
-
"dev": "nodemon src/server.js",
|
|
9
|
-
"lint": "echo 'Add linting here'",
|
|
10
|
-
"test": "echo 'Add tests here'"
|
|
11
|
-
},
|
|
12
|
-
"engines": {
|
|
13
|
-
"node": ">=18.0.0"
|
|
14
|
-
},
|
|
15
|
-
"keywords": [],
|
|
16
|
-
"author": "",
|
|
17
|
-
"license": "ISC",
|
|
18
|
-
"type": "module",
|
|
19
|
-
"dependencies": {
|
|
20
|
-
"cors": "^2.8.5",
|
|
21
|
-
"dotenv": "^16.3.1",
|
|
22
|
-
"express": "^4.18.2",
|
|
23
|
-
"zod": "^3.22.4"
|
|
24
|
-
},
|
|
25
|
-
"devDependencies": {
|
|
26
|
-
"@types/express": "^4.17.21",
|
|
27
|
-
"@types/node": "^25.0.10",
|
|
28
|
-
"nodemon": "^3.0.2",
|
|
29
|
-
"ts-node-dev": "^2.0.0",
|
|
30
|
-
"typescript": "^5.9.3"
|
|
31
|
-
}
|
|
32
|
-
}
|
package/template/ts/src/app.js
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import express from "express";
|
|
2
|
-
import cors from "cors";
|
|
3
|
-
import { env } from "./config/env.js";
|
|
4
|
-
import { HTTP_STATUS, ERROR_MESSAGES } from "./config/constants.js";
|
|
5
|
-
import { requestLogger } from "./middlewares/requestLogger.js";
|
|
6
|
-
import {
|
|
7
|
-
errorHandler,
|
|
8
|
-
asyncHandler,
|
|
9
|
-
NotFoundError,
|
|
10
|
-
} from "./middlewares/errorHandler.js";
|
|
11
|
-
import { sendSuccess } from "./utils/response.js";
|
|
12
|
-
import { logger } from "./utils/logger.js";
|
|
13
|
-
import routes from "./routes.js";
|
|
14
|
-
|
|
15
|
-
export const app = express();
|
|
16
|
-
|
|
17
|
-
// Trust proxy
|
|
18
|
-
app.set("trust proxy", 1);
|
|
19
|
-
|
|
20
|
-
// CORS Configuration
|
|
21
|
-
app.use(
|
|
22
|
-
cors({
|
|
23
|
-
origin: env.CORS_ORIGIN,
|
|
24
|
-
credentials: true,
|
|
25
|
-
methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
|
|
26
|
-
allowedHeaders: ["Content-Type", "Authorization"],
|
|
27
|
-
}),
|
|
28
|
-
);
|
|
29
|
-
|
|
30
|
-
// Body parsing middleware
|
|
31
|
-
app.use(express.json({ limit: "10mb" }));
|
|
32
|
-
app.use(express.urlencoded({ extended: true, limit: "10mb" }));
|
|
33
|
-
|
|
34
|
-
// Request timeout
|
|
35
|
-
app.use((req, res, next) => {
|
|
36
|
-
req.setTimeout(env.REQUEST_TIMEOUT);
|
|
37
|
-
res.setTimeout(env.REQUEST_TIMEOUT);
|
|
38
|
-
next();
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
// Request logging
|
|
42
|
-
app.use(requestLogger);
|
|
43
|
-
|
|
44
|
-
// API Routes
|
|
45
|
-
app.use("/api", routes);
|
|
46
|
-
|
|
47
|
-
// Root health endpoint
|
|
48
|
-
app.get(
|
|
49
|
-
"/",
|
|
50
|
-
asyncHandler(async (req, res) => {
|
|
51
|
-
sendSuccess(
|
|
52
|
-
res,
|
|
53
|
-
{
|
|
54
|
-
message: "Welcome to Charcole API",
|
|
55
|
-
version: "1.0.0",
|
|
56
|
-
environment: env.NODE_ENV,
|
|
57
|
-
},
|
|
58
|
-
200,
|
|
59
|
-
"API is online",
|
|
60
|
-
);
|
|
61
|
-
}),
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
// 404 handler
|
|
65
|
-
app.use((req, res, next) => {
|
|
66
|
-
throw new NotFoundError(`${req.method} ${req.path}`, {
|
|
67
|
-
method: req.method,
|
|
68
|
-
path: req.path,
|
|
69
|
-
});
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
// Global error handler (MUST be last)
|
|
73
|
-
app.use(errorHandler);
|
|
74
|
-
|
|
75
|
-
logger.info("Express app configured successfully");
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export const HTTP_STATUS = {
|
|
2
|
-
OK: 200,
|
|
3
|
-
CREATED: 201,
|
|
4
|
-
BAD_REQUEST: 400,
|
|
5
|
-
UNAUTHORIZED: 401,
|
|
6
|
-
FORBIDDEN: 403,
|
|
7
|
-
NOT_FOUND: 404,
|
|
8
|
-
CONFLICT: 409,
|
|
9
|
-
UNPROCESSABLE_ENTITY: 422,
|
|
10
|
-
INTERNAL_SERVER_ERROR: 500,
|
|
11
|
-
SERVICE_UNAVAILABLE: 503,
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export const ERROR_MESSAGES = {
|
|
15
|
-
VALIDATION_ERROR: "Validation failed",
|
|
16
|
-
NOT_FOUND: "Resource not found",
|
|
17
|
-
UNAUTHORIZED: "Unauthorized",
|
|
18
|
-
SERVER_ERROR: "Internal server error",
|
|
19
|
-
BAD_REQUEST: "Bad request",
|
|
20
|
-
};
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
|
|
3
|
-
const envSchema = z.object({
|
|
4
|
-
NODE_ENV: z
|
|
5
|
-
.enum(["development", "production", "test"])
|
|
6
|
-
.default("development"),
|
|
7
|
-
PORT: z.coerce.number().default(3000),
|
|
8
|
-
LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]).default("info"),
|
|
9
|
-
CORS_ORIGIN: z.string().default("*"),
|
|
10
|
-
REQUEST_TIMEOUT: z.coerce.number().default(30000),
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
const parseEnv = () => {
|
|
14
|
-
try {
|
|
15
|
-
return envSchema.parse(process.env);
|
|
16
|
-
} catch (error) {
|
|
17
|
-
console.error("❌ Invalid environment variables:", error.errors);
|
|
18
|
-
process.exit(1);
|
|
19
|
-
}
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export const env = parseEnv();
|
|
23
|
-
|
|
24
|
-
export const isDevelopment = env.NODE_ENV === "development";
|
|
25
|
-
export const isProduction = env.NODE_ENV === "production";
|
|
26
|
-
export const isTest = env.NODE_ENV === "test";
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
import { ZodError } from "zod";
|
|
2
|
-
import { HTTP_STATUS, ERROR_MESSAGES } from "../config/constants.js";
|
|
3
|
-
import { logger } from "../utils/logger.js";
|
|
4
|
-
import {
|
|
5
|
-
AppError,
|
|
6
|
-
ValidationError,
|
|
7
|
-
InternalServerError,
|
|
8
|
-
} from "../utils/AppError.js";
|
|
9
|
-
import { env } from "../config/env.js";
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Normalize different error types to AppError
|
|
13
|
-
*/
|
|
14
|
-
const normalizeError = (err) => {
|
|
15
|
-
// Already an AppError
|
|
16
|
-
if (err instanceof AppError) {
|
|
17
|
-
return err;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// Zod validation error
|
|
21
|
-
if (err instanceof ZodError) {
|
|
22
|
-
const errors = err.errors.map((e) => ({
|
|
23
|
-
field: e.path.join("."),
|
|
24
|
-
message: e.message,
|
|
25
|
-
code: e.code,
|
|
26
|
-
}));
|
|
27
|
-
return new ValidationError(ERROR_MESSAGES.VALIDATION_ERROR, errors);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Syntax errors (programmer error)
|
|
31
|
-
if (err instanceof SyntaxError) {
|
|
32
|
-
return new InternalServerError("Syntax error in application code", err, {
|
|
33
|
-
type: "SyntaxError",
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Type errors (programmer error)
|
|
38
|
-
if (err instanceof TypeError) {
|
|
39
|
-
return new InternalServerError("Type error in application", err, {
|
|
40
|
-
type: "TypeError",
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Reference errors (programmer error)
|
|
45
|
-
if (err instanceof ReferenceError) {
|
|
46
|
-
return new InternalServerError("Reference error in application", err, {
|
|
47
|
-
type: "ReferenceError",
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// Range errors
|
|
52
|
-
if (err instanceof RangeError) {
|
|
53
|
-
return new InternalServerError("Range error in application", err, {
|
|
54
|
-
type: "RangeError",
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Generic error (unknown)
|
|
59
|
-
return new InternalServerError(
|
|
60
|
-
err.message || ERROR_MESSAGES.SERVER_ERROR,
|
|
61
|
-
err,
|
|
62
|
-
{ type: "UnknownError" },
|
|
63
|
-
);
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Log error based on type (operational vs programmer)
|
|
68
|
-
*/
|
|
69
|
-
const logError = (appError, req) => {
|
|
70
|
-
const errorDetails = {
|
|
71
|
-
type: appError.isOperational ? "OPERATIONAL" : "PROGRAMMER",
|
|
72
|
-
code: appError.code,
|
|
73
|
-
message: appError.message,
|
|
74
|
-
statusCode: appError.statusCode,
|
|
75
|
-
method: req.method,
|
|
76
|
-
path: req.path,
|
|
77
|
-
query: req.query,
|
|
78
|
-
ip: req.ip,
|
|
79
|
-
userAgent: req.get("user-agent"),
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
// Operational errors: normal logging
|
|
83
|
-
if (appError.isOperational) {
|
|
84
|
-
logger.warn(`Operational Error: ${appError.code}`, errorDetails);
|
|
85
|
-
} else {
|
|
86
|
-
// Programmer errors: detailed logging with stack
|
|
87
|
-
logger.error(
|
|
88
|
-
`Programmer Error: ${appError.code}`,
|
|
89
|
-
errorDetails,
|
|
90
|
-
appError.stack,
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Log validation errors separately
|
|
95
|
-
if (appError instanceof ValidationError && appError.errors) {
|
|
96
|
-
logger.debug("Validation errors", { errors: appError.errors });
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Log cause if present
|
|
100
|
-
if (appError.cause) {
|
|
101
|
-
logger.debug("Error cause", { cause: appError.cause.message });
|
|
102
|
-
}
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Send error response
|
|
107
|
-
*/
|
|
108
|
-
const sendErrorResponse = (res, appError) => {
|
|
109
|
-
const statusCode = appError.statusCode || HTTP_STATUS.INTERNAL_SERVER_ERROR;
|
|
110
|
-
|
|
111
|
-
// In production, hide internal details for programmer errors
|
|
112
|
-
if (!appError.isOperational && env.isProduction) {
|
|
113
|
-
return res.status(statusCode).json({
|
|
114
|
-
success: false,
|
|
115
|
-
message: ERROR_MESSAGES.SERVER_ERROR,
|
|
116
|
-
code: "INTERNAL_SERVER_ERROR",
|
|
117
|
-
timestamp: new Date().toISOString(),
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Return detailed error in development
|
|
122
|
-
const response = appError.toJSON
|
|
123
|
-
? appError.toJSON()
|
|
124
|
-
: {
|
|
125
|
-
success: false,
|
|
126
|
-
message: appError.message,
|
|
127
|
-
code: appError.code,
|
|
128
|
-
statusCode,
|
|
129
|
-
timestamp: new Date().toISOString(),
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
return res.status(statusCode).json(response);
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Global Error Handler Middleware
|
|
137
|
-
*
|
|
138
|
-
* This middleware catches all errors and provides a unified way to handle them.
|
|
139
|
-
* MUST be the last middleware in the app.
|
|
140
|
-
*
|
|
141
|
-
* Features:
|
|
142
|
-
* - Distinguishes between operational and programmer errors
|
|
143
|
-
* - Logs errors with full context
|
|
144
|
-
* - Hides sensitive info in production
|
|
145
|
-
* - Formats JSON responses consistently
|
|
146
|
-
*/
|
|
147
|
-
export const errorHandler = (err, req, res, next) => {
|
|
148
|
-
// Normalize the error
|
|
149
|
-
const appError = normalizeError(err);
|
|
150
|
-
|
|
151
|
-
// Log the error
|
|
152
|
-
logError(appError, req);
|
|
153
|
-
|
|
154
|
-
// Send response
|
|
155
|
-
sendErrorResponse(res, appError);
|
|
156
|
-
};
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Async error wrapper
|
|
160
|
-
* Wrap async route handlers to catch errors and pass to middleware
|
|
161
|
-
*
|
|
162
|
-
* Example:
|
|
163
|
-
* router.get('/users/:id', asyncHandler(getUserHandler))
|
|
164
|
-
*/
|
|
165
|
-
export const asyncHandler = (fn) => {
|
|
166
|
-
return (req, res, next) => {
|
|
167
|
-
return Promise.resolve(fn(req, res, next)).catch(next);
|
|
168
|
-
};
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
export {
|
|
172
|
-
AppError,
|
|
173
|
-
ValidationError,
|
|
174
|
-
InternalServerError,
|
|
175
|
-
AuthenticationError,
|
|
176
|
-
AuthorizationError,
|
|
177
|
-
NotFoundError,
|
|
178
|
-
ConflictError,
|
|
179
|
-
BadRequestError,
|
|
180
|
-
} from "../utils/AppError.js";
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { logger } from "../utils/logger.js";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Request logging middleware
|
|
5
|
-
* Logs all HTTP requests with method, path, status, duration, and IP
|
|
6
|
-
*/
|
|
7
|
-
export const requestLogger = (req, res, next) => {
|
|
8
|
-
const start = Date.now();
|
|
9
|
-
|
|
10
|
-
res.on("finish", () => {
|
|
11
|
-
const duration = Date.now() - start;
|
|
12
|
-
const statusCode = res.statusCode;
|
|
13
|
-
const isError = statusCode >= 400;
|
|
14
|
-
|
|
15
|
-
const logData = {
|
|
16
|
-
method: req.method,
|
|
17
|
-
path: req.path,
|
|
18
|
-
statusCode,
|
|
19
|
-
durationMs: duration,
|
|
20
|
-
ip: req.ip,
|
|
21
|
-
userAgent: req.get("user-agent"),
|
|
22
|
-
...(isError && { error: true }),
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
if (isError) {
|
|
26
|
-
logger.warn(`${req.method} ${req.path}`, logData);
|
|
27
|
-
} else {
|
|
28
|
-
logger.debug(`${req.method} ${req.path}`, logData);
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
next();
|
|
33
|
-
};
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { ValidationError } from "../utils/AppError.js";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Request validation middleware
|
|
5
|
-
*
|
|
6
|
-
* Validates request body, query, and params against a Zod schema
|
|
7
|
-
* Throws ValidationError if validation fails
|
|
8
|
-
*
|
|
9
|
-
* Example:
|
|
10
|
-
* const schema = z.object({
|
|
11
|
-
* body: z.object({ name: z.string() }),
|
|
12
|
-
* query: z.object({ page: z.coerce.number().optional() }),
|
|
13
|
-
* });
|
|
14
|
-
*
|
|
15
|
-
* router.post('/items', validateRequest(schema), handler)
|
|
16
|
-
*/
|
|
17
|
-
export const validateRequest = (schema) => {
|
|
18
|
-
return async (req, res, next) => {
|
|
19
|
-
try {
|
|
20
|
-
// Validate request
|
|
21
|
-
const validated = await schema.parseAsync({
|
|
22
|
-
body: req.body,
|
|
23
|
-
query: req.query,
|
|
24
|
-
params: req.params,
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
// Attach validated data
|
|
28
|
-
req.validatedData = validated;
|
|
29
|
-
next();
|
|
30
|
-
} catch (error) {
|
|
31
|
-
if (error.name === "ZodError") {
|
|
32
|
-
const errors = error.errors.map((e) => ({
|
|
33
|
-
field: e.path.join("."),
|
|
34
|
-
message: e.message,
|
|
35
|
-
code: e.code,
|
|
36
|
-
}));
|
|
37
|
-
throw new ValidationError("Request validation failed", errors);
|
|
38
|
-
}
|
|
39
|
-
next(error);
|
|
40
|
-
}
|
|
41
|
-
};
|
|
42
|
-
};
|