response-kit 1.0.0 → 2.0.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 +557 -146
- package/docs/error-response.md +582 -101
- package/docs/getting-started.md +436 -38
- package/docs/pagination.md +542 -77
- package/docs/success-response.md +326 -61
- package/index.d.ts +122 -20
- package/package.json +67 -51
- package/src/core/config.js +94 -0
- package/src/helpers/error.js +104 -0
- package/src/helpers/pagination.js +41 -0
- package/src/helpers/success.js +52 -0
- package/src/helpers/validation.js +32 -0
- package/src/index.js +83 -6
- package/src/middleware/asyncHandler.js +14 -0
- package/src/middleware/response.js +69 -0
- package/src/types/index.js +71 -0
- package/src/utils/constants.js +39 -0
package/docs/error-response.md
CHANGED
|
@@ -1,222 +1,703 @@
|
|
|
1
1
|
# Error Responses
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This document covers all error response helpers in Response Kit v2.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## Overview
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Response Kit provides standardized error responses for common HTTP error status codes:
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
| Method | Status Code | Use Case |
|
|
12
|
+
|--------|-------------|----------|
|
|
13
|
+
| `badRequest()` | 400 | Invalid request data |
|
|
14
|
+
| `unauthorized()` | 401 | Authentication required |
|
|
15
|
+
| `forbidden()` | 403 | Insufficient permissions |
|
|
16
|
+
| `notFound()` | 404 | Resource not found |
|
|
17
|
+
| `conflict()` | 409 | Resource conflict |
|
|
18
|
+
| `validationError()` | 422 | Validation failures |
|
|
19
|
+
| `internalError()` | 500 | Server errors |
|
|
20
|
+
|
|
21
|
+
All methods are available in two ways:
|
|
22
|
+
- **Middleware API** (v2): `res.notFound(message)`
|
|
23
|
+
- **Direct API** (v1): `response.notFound(res, message)`
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## badRequest()
|
|
28
|
+
|
|
29
|
+
Use when the client sends invalid or malformed request data.
|
|
30
|
+
|
|
31
|
+
### Using Middleware (Recommended)
|
|
32
|
+
|
|
33
|
+
```js
|
|
34
|
+
app.use(response.middleware());
|
|
35
|
+
|
|
36
|
+
app.post('/users', (req, res) => {
|
|
37
|
+
if (!req.body.email) {
|
|
38
|
+
return res.badRequest('Email is required');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Process request...
|
|
42
|
+
});
|
|
13
43
|
```
|
|
14
44
|
|
|
15
|
-
|
|
45
|
+
### Using Direct API
|
|
16
46
|
|
|
17
47
|
```js
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
48
|
+
app.post('/users', (req, res) => {
|
|
49
|
+
if (!req.body.email) {
|
|
50
|
+
return response.badRequest(res, 'Email is required');
|
|
51
|
+
}
|
|
52
|
+
});
|
|
22
53
|
```
|
|
23
54
|
|
|
24
|
-
|
|
55
|
+
### Response
|
|
25
56
|
|
|
26
57
|
```json
|
|
27
58
|
{
|
|
28
59
|
"success": false,
|
|
29
|
-
"message": "
|
|
30
|
-
"errors": null
|
|
60
|
+
"message": "Email is required"
|
|
31
61
|
}
|
|
32
62
|
```
|
|
33
63
|
|
|
34
|
-
|
|
64
|
+
**Status Code:** `400 Bad Request`
|
|
35
65
|
|
|
36
|
-
|
|
66
|
+
### Common Use Cases
|
|
37
67
|
|
|
38
|
-
|
|
68
|
+
- Missing required parameters
|
|
69
|
+
- Invalid parameter format
|
|
70
|
+
- Malformed JSON payload
|
|
71
|
+
- Invalid query strings
|
|
39
72
|
|
|
40
|
-
|
|
41
|
-
|
|
73
|
+
### Example
|
|
74
|
+
|
|
75
|
+
```js
|
|
76
|
+
app.get('/users', response.asyncHandler(async (req, res) => {
|
|
77
|
+
const { page, limit } = req.query;
|
|
78
|
+
|
|
79
|
+
if (page && isNaN(page)) {
|
|
80
|
+
return res.badRequest('Page must be a number');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (limit && (limit < 1 || limit > 100)) {
|
|
84
|
+
return res.badRequest('Limit must be between 1 and 100');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const users = await User.paginate(page, limit);
|
|
88
|
+
res.success(users);
|
|
89
|
+
}));
|
|
42
90
|
```
|
|
43
91
|
|
|
44
|
-
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## unauthorized()
|
|
95
|
+
|
|
96
|
+
Use when authentication is required but not provided or invalid.
|
|
97
|
+
|
|
98
|
+
### Using Middleware
|
|
45
99
|
|
|
46
100
|
```js
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
101
|
+
app.get('/profile', (req, res) => {
|
|
102
|
+
if (!req.headers.authorization) {
|
|
103
|
+
return res.unauthorized('Authentication token required');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Verify token and proceed...
|
|
107
|
+
});
|
|
51
108
|
```
|
|
52
109
|
|
|
53
|
-
|
|
110
|
+
### Response
|
|
54
111
|
|
|
55
112
|
```json
|
|
56
113
|
{
|
|
57
114
|
"success": false,
|
|
58
|
-
"message": "
|
|
59
|
-
"errors": null
|
|
115
|
+
"message": "Authentication token required"
|
|
60
116
|
}
|
|
61
117
|
```
|
|
62
118
|
|
|
63
|
-
|
|
119
|
+
**Status Code:** `401 Unauthorized`
|
|
64
120
|
|
|
65
|
-
|
|
121
|
+
### Common Use Cases
|
|
66
122
|
|
|
67
|
-
|
|
123
|
+
- Missing authentication token
|
|
124
|
+
- Invalid credentials
|
|
125
|
+
- Expired token
|
|
126
|
+
- Invalid API key
|
|
68
127
|
|
|
69
|
-
|
|
70
|
-
|
|
128
|
+
### Example: Protected Route
|
|
129
|
+
|
|
130
|
+
```js
|
|
131
|
+
function authenticate(req, res, next) {
|
|
132
|
+
const token = req.headers.authorization?.split(' ')[1];
|
|
133
|
+
|
|
134
|
+
if (!token) {
|
|
135
|
+
return res.unauthorized('Access token required');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
req.user = jwt.verify(token, process.env.JWT_SECRET);
|
|
140
|
+
next();
|
|
141
|
+
} catch (error) {
|
|
142
|
+
return res.unauthorized('Invalid or expired token');
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
app.get('/profile', authenticate, response.asyncHandler(async (req, res) => {
|
|
147
|
+
const user = await User.findById(req.user.id);
|
|
148
|
+
res.success(user);
|
|
149
|
+
}));
|
|
71
150
|
```
|
|
72
151
|
|
|
73
|
-
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## forbidden()
|
|
155
|
+
|
|
156
|
+
Use when the user is authenticated but doesn't have permission.
|
|
157
|
+
|
|
158
|
+
### Using Middleware
|
|
74
159
|
|
|
75
160
|
```js
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
161
|
+
app.delete('/users/:id', (req, res) => {
|
|
162
|
+
if (req.user.role !== 'admin') {
|
|
163
|
+
return res.forbidden('Admin access required');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Proceed with deletion...
|
|
167
|
+
});
|
|
80
168
|
```
|
|
81
169
|
|
|
82
|
-
|
|
170
|
+
### Response
|
|
83
171
|
|
|
84
172
|
```json
|
|
85
173
|
{
|
|
86
174
|
"success": false,
|
|
87
|
-
"message": "
|
|
88
|
-
"errors": null
|
|
175
|
+
"message": "Admin access required"
|
|
89
176
|
}
|
|
90
177
|
```
|
|
91
178
|
|
|
92
|
-
|
|
179
|
+
**Status Code:** `403 Forbidden`
|
|
180
|
+
|
|
181
|
+
### Common Use Cases
|
|
93
182
|
|
|
94
|
-
|
|
183
|
+
- Insufficient role/permissions
|
|
184
|
+
- Resource ownership mismatch
|
|
185
|
+
- Account limitations
|
|
186
|
+
- Feature access restrictions
|
|
95
187
|
|
|
96
|
-
|
|
188
|
+
### Example: Role-Based Access Control
|
|
97
189
|
|
|
98
|
-
```
|
|
99
|
-
|
|
190
|
+
```js
|
|
191
|
+
function requireRole(role) {
|
|
192
|
+
return (req, res, next) => {
|
|
193
|
+
if (req.user.role !== role) {
|
|
194
|
+
return res.forbidden(`${role} access required`);
|
|
195
|
+
}
|
|
196
|
+
next();
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
app.delete('/posts/:id',
|
|
201
|
+
authenticate,
|
|
202
|
+
requireRole('admin'),
|
|
203
|
+
response.asyncHandler(async (req, res) => {
|
|
204
|
+
await Post.findByIdAndDelete(req.params.id);
|
|
205
|
+
res.success(null, 'Post deleted successfully');
|
|
206
|
+
})
|
|
207
|
+
);
|
|
100
208
|
```
|
|
101
209
|
|
|
102
|
-
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## notFound()
|
|
213
|
+
|
|
214
|
+
Use when a requested resource doesn't exist.
|
|
215
|
+
|
|
216
|
+
### Using Middleware
|
|
103
217
|
|
|
104
218
|
```js
|
|
105
|
-
response.
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
)
|
|
219
|
+
app.get('/users/:id', response.asyncHandler(async (req, res) => {
|
|
220
|
+
const user = await User.findById(req.params.id);
|
|
221
|
+
|
|
222
|
+
if (!user) {
|
|
223
|
+
return res.notFound('User not found');
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
res.success(user);
|
|
227
|
+
}));
|
|
109
228
|
```
|
|
110
229
|
|
|
111
|
-
|
|
230
|
+
### Response
|
|
112
231
|
|
|
113
232
|
```json
|
|
114
233
|
{
|
|
115
234
|
"success": false,
|
|
116
|
-
"message": "User
|
|
117
|
-
"errors": null
|
|
235
|
+
"message": "User not found"
|
|
118
236
|
}
|
|
119
237
|
```
|
|
120
238
|
|
|
121
|
-
|
|
239
|
+
**Status Code:** `404 Not Found`
|
|
240
|
+
|
|
241
|
+
### Common Use Cases
|
|
122
242
|
|
|
123
|
-
|
|
243
|
+
- Resource ID doesn't exist
|
|
244
|
+
- Route not found
|
|
245
|
+
- File not found
|
|
246
|
+
- Record deleted
|
|
124
247
|
|
|
125
|
-
|
|
248
|
+
### Example: 404 Handler
|
|
126
249
|
|
|
127
|
-
```
|
|
128
|
-
|
|
250
|
+
```js
|
|
251
|
+
// Route handlers...
|
|
252
|
+
|
|
253
|
+
// 404 handler (place at the end)
|
|
254
|
+
app.use((req, res) => {
|
|
255
|
+
res.notFound(`Route ${req.method} ${req.path} not found`);
|
|
256
|
+
});
|
|
129
257
|
```
|
|
130
258
|
|
|
131
|
-
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## conflict()
|
|
262
|
+
|
|
263
|
+
Use when there's a conflict with the current state of the resource.
|
|
264
|
+
|
|
265
|
+
### Using Middleware
|
|
132
266
|
|
|
133
267
|
```js
|
|
134
|
-
response.
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
)
|
|
268
|
+
app.post('/users', response.asyncHandler(async (req, res) => {
|
|
269
|
+
const existing = await User.findOne({ email: req.body.email });
|
|
270
|
+
|
|
271
|
+
if (existing) {
|
|
272
|
+
return res.conflict('User with this email already exists');
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const user = await User.create(req.body);
|
|
276
|
+
res.created(user);
|
|
277
|
+
}));
|
|
138
278
|
```
|
|
139
279
|
|
|
140
|
-
|
|
280
|
+
### Response
|
|
141
281
|
|
|
142
282
|
```json
|
|
143
283
|
{
|
|
144
284
|
"success": false,
|
|
145
|
-
"message": "
|
|
146
|
-
"errors": null
|
|
285
|
+
"message": "User with this email already exists"
|
|
147
286
|
}
|
|
148
287
|
```
|
|
149
288
|
|
|
289
|
+
**Status Code:** `409 Conflict`
|
|
290
|
+
|
|
291
|
+
### Common Use Cases
|
|
292
|
+
|
|
293
|
+
- Duplicate unique field (email, username)
|
|
294
|
+
- Resource version mismatch
|
|
295
|
+
- Concurrent modification conflict
|
|
296
|
+
- State transition conflict
|
|
297
|
+
|
|
298
|
+
### Example: Username Availability
|
|
299
|
+
|
|
300
|
+
```js
|
|
301
|
+
app.post('/check-username', response.asyncHandler(async (req, res) => {
|
|
302
|
+
const { username } = req.body;
|
|
303
|
+
|
|
304
|
+
const existing = await User.findOne({ username });
|
|
305
|
+
|
|
306
|
+
if (existing) {
|
|
307
|
+
return res.conflict('Username is already taken');
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
res.success(null, 'Username is available');
|
|
311
|
+
}));
|
|
312
|
+
```
|
|
313
|
+
|
|
150
314
|
---
|
|
151
315
|
|
|
152
|
-
|
|
316
|
+
## validationError()
|
|
153
317
|
|
|
154
|
-
|
|
318
|
+
Use when request validation fails. This helper is specifically designed for detailed validation errors.
|
|
155
319
|
|
|
156
|
-
|
|
157
|
-
|
|
320
|
+
### Using Middleware
|
|
321
|
+
|
|
322
|
+
```js
|
|
323
|
+
app.post('/users', response.asyncHandler(async (req, res) => {
|
|
324
|
+
const { name, email, password } = req.body;
|
|
325
|
+
|
|
326
|
+
const errors = {};
|
|
327
|
+
|
|
328
|
+
if (!name) errors.name = 'Name is required';
|
|
329
|
+
if (!email) errors.email = 'Email is required';
|
|
330
|
+
else if (!isValidEmail(email)) errors.email = 'Email is invalid';
|
|
331
|
+
|
|
332
|
+
if (!password) errors.password = 'Password is required';
|
|
333
|
+
else if (password.length < 8) errors.password = 'Password must be at least 8 characters';
|
|
334
|
+
|
|
335
|
+
if (Object.keys(errors).length > 0) {
|
|
336
|
+
return res.validationError(errors, 'Validation failed');
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const user = await User.create({ name, email, password });
|
|
340
|
+
res.created(user);
|
|
341
|
+
}));
|
|
158
342
|
```
|
|
159
343
|
|
|
160
|
-
|
|
344
|
+
### Syntax
|
|
161
345
|
|
|
346
|
+
**Middleware API:**
|
|
162
347
|
```js
|
|
163
|
-
|
|
164
|
-
res,
|
|
165
|
-
"Something Went Wrong"
|
|
166
|
-
);
|
|
348
|
+
res.validationError(errors, message)
|
|
167
349
|
```
|
|
168
350
|
|
|
169
|
-
|
|
351
|
+
**Direct API:**
|
|
352
|
+
```js
|
|
353
|
+
response.validationError(res, errors, message)
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### Parameters
|
|
357
|
+
|
|
358
|
+
| Parameter | Type | Required | Default | Description |
|
|
359
|
+
|-----------|------|----------|---------|-------------|
|
|
360
|
+
| `errors` | object | No | `{}` | Validation error details |
|
|
361
|
+
| `message` | string | No | `'Validation Failed'` | Error message |
|
|
362
|
+
|
|
363
|
+
### Response
|
|
170
364
|
|
|
171
365
|
```json
|
|
172
366
|
{
|
|
173
367
|
"success": false,
|
|
174
|
-
"message": "
|
|
175
|
-
"errors":
|
|
368
|
+
"message": "Validation failed",
|
|
369
|
+
"errors": {
|
|
370
|
+
"name": "Name is required",
|
|
371
|
+
"email": "Email is invalid",
|
|
372
|
+
"password": "Password must be at least 8 characters"
|
|
373
|
+
}
|
|
176
374
|
}
|
|
177
375
|
```
|
|
178
376
|
|
|
377
|
+
**Status Code:** `422 Unprocessable Entity`
|
|
378
|
+
|
|
379
|
+
### Example: Complex Validation
|
|
380
|
+
|
|
381
|
+
```js
|
|
382
|
+
app.post('/register', response.asyncHandler(async (req, res) => {
|
|
383
|
+
const { username, email, password, confirmPassword, age } = req.body;
|
|
384
|
+
|
|
385
|
+
const errors = {};
|
|
386
|
+
|
|
387
|
+
// Username validation
|
|
388
|
+
if (!username) {
|
|
389
|
+
errors.username = 'Username is required';
|
|
390
|
+
} else if (username.length < 3) {
|
|
391
|
+
errors.username = 'Username must be at least 3 characters';
|
|
392
|
+
} else if (!/^[a-zA-Z0-9_]+$/.test(username)) {
|
|
393
|
+
errors.username = 'Username can only contain letters, numbers, and underscores';
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Email validation
|
|
397
|
+
if (!email) {
|
|
398
|
+
errors.email = 'Email is required';
|
|
399
|
+
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
|
400
|
+
errors.email = 'Invalid email format';
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Password validation
|
|
404
|
+
if (!password) {
|
|
405
|
+
errors.password = 'Password is required';
|
|
406
|
+
} else if (password.length < 8) {
|
|
407
|
+
errors.password = 'Password must be at least 8 characters';
|
|
408
|
+
} else if (!/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(password)) {
|
|
409
|
+
errors.password = 'Password must contain uppercase, lowercase, and number';
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Confirm password
|
|
413
|
+
if (password !== confirmPassword) {
|
|
414
|
+
errors.confirmPassword = 'Passwords do not match';
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Age validation
|
|
418
|
+
if (!age) {
|
|
419
|
+
errors.age = 'Age is required';
|
|
420
|
+
} else if (age < 18) {
|
|
421
|
+
errors.age = 'You must be at least 18 years old';
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
if (Object.keys(errors).length > 0) {
|
|
425
|
+
return res.validationError(errors);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// Proceed with registration...
|
|
429
|
+
const user = await User.create({ username, email, password, age });
|
|
430
|
+
res.created(user, 'Registration successful');
|
|
431
|
+
}));
|
|
432
|
+
```
|
|
433
|
+
|
|
179
434
|
---
|
|
180
435
|
|
|
181
|
-
|
|
436
|
+
## internalError()
|
|
182
437
|
|
|
183
|
-
|
|
438
|
+
Use for unexpected server errors.
|
|
184
439
|
|
|
185
|
-
|
|
440
|
+
### Using Middleware
|
|
186
441
|
|
|
187
442
|
```js
|
|
188
|
-
response.
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
)
|
|
443
|
+
app.get('/users', response.asyncHandler(async (req, res) => {
|
|
444
|
+
const users = await User.find(); // If this throws, async handler catches it
|
|
445
|
+
res.success(users);
|
|
446
|
+
}));
|
|
447
|
+
|
|
448
|
+
// Global error handler
|
|
449
|
+
app.use((err, req, res, next) => {
|
|
450
|
+
console.error('Error:', err);
|
|
451
|
+
res.internalError(err.message || 'Internal server error');
|
|
452
|
+
});
|
|
195
453
|
```
|
|
196
454
|
|
|
197
|
-
|
|
455
|
+
### Response
|
|
198
456
|
|
|
199
457
|
```json
|
|
200
458
|
{
|
|
201
459
|
"success": false,
|
|
202
|
-
"message": "
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
460
|
+
"message": "Database connection failed"
|
|
461
|
+
}
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
**Status Code:** `500 Internal Server Error`
|
|
465
|
+
|
|
466
|
+
### Common Use Cases
|
|
467
|
+
|
|
468
|
+
- Database connection errors
|
|
469
|
+
- Unexpected exceptions
|
|
470
|
+
- Third-party API failures
|
|
471
|
+
- File system errors
|
|
472
|
+
|
|
473
|
+
### Example: Global Error Handler
|
|
474
|
+
|
|
475
|
+
```js
|
|
476
|
+
const express = require('express');
|
|
477
|
+
const response = require('response-kit');
|
|
478
|
+
|
|
479
|
+
const app = express();
|
|
480
|
+
|
|
481
|
+
app.use(response.middleware());
|
|
482
|
+
|
|
483
|
+
// Routes...
|
|
484
|
+
|
|
485
|
+
// Global error handler (must be last)
|
|
486
|
+
app.use((err, req, res, next) => {
|
|
487
|
+
console.error('Unhandled error:', err);
|
|
488
|
+
|
|
489
|
+
// Don't expose internal error details in production
|
|
490
|
+
const message = process.env.NODE_ENV === 'production'
|
|
491
|
+
? 'Internal server error'
|
|
492
|
+
: err.message;
|
|
493
|
+
|
|
494
|
+
res.internalError(message);
|
|
495
|
+
});
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
---
|
|
499
|
+
|
|
500
|
+
## Complete Example
|
|
501
|
+
|
|
502
|
+
Here's a complete CRUD API with proper error handling:
|
|
503
|
+
|
|
504
|
+
```js
|
|
505
|
+
const express = require('express');
|
|
506
|
+
const response = require('response-kit');
|
|
507
|
+
|
|
508
|
+
const app = express();
|
|
509
|
+
|
|
510
|
+
app.use(express.json());
|
|
511
|
+
app.use(response.middleware());
|
|
512
|
+
|
|
513
|
+
// Get all users
|
|
514
|
+
app.get('/users', response.asyncHandler(async (req, res) => {
|
|
515
|
+
const users = await User.find();
|
|
516
|
+
res.success(users, 'Users fetched successfully');
|
|
517
|
+
}));
|
|
518
|
+
|
|
519
|
+
// Get user by ID
|
|
520
|
+
app.get('/users/:id', response.asyncHandler(async (req, res) => {
|
|
521
|
+
const user = await User.findById(req.params.id);
|
|
522
|
+
|
|
523
|
+
if (!user) {
|
|
524
|
+
return res.notFound('User not found');
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
res.success(user);
|
|
528
|
+
}));
|
|
529
|
+
|
|
530
|
+
// Create user
|
|
531
|
+
app.post('/users', response.asyncHandler(async (req, res) => {
|
|
532
|
+
const { name, email, password } = req.body;
|
|
533
|
+
|
|
534
|
+
// Validation
|
|
535
|
+
const errors = {};
|
|
536
|
+
if (!name) errors.name = 'Name is required';
|
|
537
|
+
if (!email) errors.email = 'Email is required';
|
|
538
|
+
if (!password) errors.password = 'Password is required';
|
|
539
|
+
|
|
540
|
+
if (Object.keys(errors).length > 0) {
|
|
541
|
+
return res.validationError(errors);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// Check for duplicate
|
|
545
|
+
const existing = await User.findOne({ email });
|
|
546
|
+
if (existing) {
|
|
547
|
+
return res.conflict('Email already exists');
|
|
206
548
|
}
|
|
549
|
+
|
|
550
|
+
// Create
|
|
551
|
+
const user = await User.create({ name, email, password });
|
|
552
|
+
res.created(user, 'User created successfully');
|
|
553
|
+
}));
|
|
554
|
+
|
|
555
|
+
// Update user
|
|
556
|
+
app.put('/users/:id',
|
|
557
|
+
authenticate,
|
|
558
|
+
response.asyncHandler(async (req, res) => {
|
|
559
|
+
const user = await User.findById(req.params.id);
|
|
560
|
+
|
|
561
|
+
if (!user) {
|
|
562
|
+
return res.notFound('User not found');
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// Check ownership
|
|
566
|
+
if (user._id.toString() !== req.user.id && req.user.role !== 'admin') {
|
|
567
|
+
return res.forbidden('You can only update your own profile');
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
Object.assign(user, req.body);
|
|
571
|
+
await user.save();
|
|
572
|
+
|
|
573
|
+
res.success(user, 'User updated successfully');
|
|
574
|
+
})
|
|
575
|
+
);
|
|
576
|
+
|
|
577
|
+
// Delete user
|
|
578
|
+
app.delete('/users/:id',
|
|
579
|
+
authenticate,
|
|
580
|
+
requireRole('admin'),
|
|
581
|
+
response.asyncHandler(async (req, res) => {
|
|
582
|
+
const user = await User.findByIdAndDelete(req.params.id);
|
|
583
|
+
|
|
584
|
+
if (!user) {
|
|
585
|
+
return res.notFound('User not found');
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
res.success(null, 'User deleted successfully');
|
|
589
|
+
})
|
|
590
|
+
);
|
|
591
|
+
|
|
592
|
+
// 404 handler
|
|
593
|
+
app.use((req, res) => {
|
|
594
|
+
res.notFound('Endpoint not found');
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
// Error handler
|
|
598
|
+
app.use((err, req, res, next) => {
|
|
599
|
+
console.error(err);
|
|
600
|
+
res.internalError(err.message);
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
app.listen(3000);
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
---
|
|
607
|
+
|
|
608
|
+
## Best Practices
|
|
609
|
+
|
|
610
|
+
### ✅ Do's
|
|
611
|
+
|
|
612
|
+
- Use specific error methods for each scenario
|
|
613
|
+
- Provide clear, actionable error messages
|
|
614
|
+
- Return validation errors as objects with field-level details
|
|
615
|
+
- Implement global error handler for uncaught errors
|
|
616
|
+
- Log errors server-side for debugging
|
|
617
|
+
|
|
618
|
+
```js
|
|
619
|
+
// ✅ Good
|
|
620
|
+
if (!user) {
|
|
621
|
+
return res.notFound('User with ID ' + id + ' not found');
|
|
207
622
|
}
|
|
623
|
+
|
|
624
|
+
res.validationError({
|
|
625
|
+
email: 'Email format is invalid',
|
|
626
|
+
age: 'Age must be at least 18'
|
|
627
|
+
});
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
### ❌ Don'ts
|
|
631
|
+
|
|
632
|
+
- Don't expose sensitive error details to clients
|
|
633
|
+
- Don't use generic messages for all errors
|
|
634
|
+
- Don't return stack traces in production
|
|
635
|
+
- Don't forget to return after sending error response
|
|
636
|
+
|
|
637
|
+
```js
|
|
638
|
+
// ❌ Bad
|
|
639
|
+
res.internalError(err.stack); // Exposes internal details
|
|
640
|
+
|
|
641
|
+
if (!user) {
|
|
642
|
+
res.notFound('Not found');
|
|
643
|
+
// Missing return - code continues executing!
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
res.badRequest('Error'); // Too generic
|
|
208
647
|
```
|
|
209
648
|
|
|
210
649
|
---
|
|
211
650
|
|
|
212
|
-
|
|
651
|
+
## With Configuration
|
|
652
|
+
|
|
653
|
+
Error responses also respect global configuration:
|
|
654
|
+
|
|
655
|
+
```js
|
|
656
|
+
response.configure({
|
|
657
|
+
successKey: 'status',
|
|
658
|
+
messageKey: 'msg',
|
|
659
|
+
errorKey: 'validationErrors',
|
|
660
|
+
includeTimestamp: true,
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
res.validationError({ email: 'Required' });
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
**Response:**
|
|
667
|
+
```json
|
|
668
|
+
{
|
|
669
|
+
"status": false,
|
|
670
|
+
"msg": "Validation Failed",
|
|
671
|
+
"validationErrors": {
|
|
672
|
+
"email": "Required"
|
|
673
|
+
},
|
|
674
|
+
"timestamp": "2024-01-15T10:30:00.000Z"
|
|
675
|
+
}
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
---
|
|
679
|
+
|
|
680
|
+
## TypeScript
|
|
681
|
+
|
|
682
|
+
```typescript
|
|
683
|
+
import { ExtendedResponse } from 'response-kit';
|
|
684
|
+
import { Request } from 'express';
|
|
685
|
+
|
|
686
|
+
app.get('/users/:id', async (req: Request, res: ExtendedResponse) => {
|
|
687
|
+
const user = await User.findById(req.params.id);
|
|
688
|
+
|
|
689
|
+
if (!user) {
|
|
690
|
+
return res.notFound('User not found');
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
res.success(user);
|
|
694
|
+
});
|
|
695
|
+
```
|
|
696
|
+
|
|
697
|
+
---
|
|
213
698
|
|
|
214
|
-
|
|
699
|
+
## Related
|
|
215
700
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
* notFound() when resource doesn't exist
|
|
220
|
-
* conflict() for duplicate data
|
|
221
|
-
* validationError() for validation failures
|
|
222
|
-
* internalError() for server errors
|
|
701
|
+
- [Success Responses](./success-response.md)
|
|
702
|
+
- [Pagination](./pagination.md)
|
|
703
|
+
- [Getting Started](./getting-started.md)
|