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.
@@ -1,222 +1,703 @@
1
1
  # Error Responses
2
2
 
3
- Error helpers make API error responses consistent across your application.
3
+ This document covers all error response helpers in Response Kit v2.
4
4
 
5
5
  ---
6
6
 
7
- # badRequest()
7
+ ## Overview
8
8
 
9
- Status Code:
9
+ Response Kit provides standardized error responses for common HTTP error status codes:
10
10
 
11
- ```http
12
- 400 Bad Request
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
- Example:
45
+ ### Using Direct API
16
46
 
17
47
  ```js
18
- response.badRequest(
19
- res,
20
- "Invalid Request Body"
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
- Output:
55
+ ### Response
25
56
 
26
57
  ```json
27
58
  {
28
59
  "success": false,
29
- "message": "Invalid Request Body",
30
- "errors": null
60
+ "message": "Email is required"
31
61
  }
32
62
  ```
33
63
 
34
- ---
64
+ **Status Code:** `400 Bad Request`
35
65
 
36
- # unauthorized()
66
+ ### Common Use Cases
37
67
 
38
- Status Code:
68
+ - Missing required parameters
69
+ - Invalid parameter format
70
+ - Malformed JSON payload
71
+ - Invalid query strings
39
72
 
40
- ```http
41
- 401 Unauthorized
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
- Example:
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
- response.unauthorized(
48
- res,
49
- "Invalid Token"
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
- Output:
110
+ ### Response
54
111
 
55
112
  ```json
56
113
  {
57
114
  "success": false,
58
- "message": "Invalid Token",
59
- "errors": null
115
+ "message": "Authentication token required"
60
116
  }
61
117
  ```
62
118
 
63
- ---
119
+ **Status Code:** `401 Unauthorized`
64
120
 
65
- # forbidden()
121
+ ### Common Use Cases
66
122
 
67
- Status Code:
123
+ - Missing authentication token
124
+ - Invalid credentials
125
+ - Expired token
126
+ - Invalid API key
68
127
 
69
- ```http
70
- 403 Forbidden
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
- Example:
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
- response.forbidden(
77
- res,
78
- "Access Denied"
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
- Output:
170
+ ### Response
83
171
 
84
172
  ```json
85
173
  {
86
174
  "success": false,
87
- "message": "Access Denied",
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
- # notFound()
183
+ - Insufficient role/permissions
184
+ - Resource ownership mismatch
185
+ - Account limitations
186
+ - Feature access restrictions
95
187
 
96
- Status Code:
188
+ ### Example: Role-Based Access Control
97
189
 
98
- ```http
99
- 404 Not Found
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
- Example:
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.notFound(
106
- res,
107
- "User Not Found"
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
- Output:
230
+ ### Response
112
231
 
113
232
  ```json
114
233
  {
115
234
  "success": false,
116
- "message": "User Not Found",
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
- # conflict()
243
+ - Resource ID doesn't exist
244
+ - Route not found
245
+ - File not found
246
+ - Record deleted
124
247
 
125
- Status Code:
248
+ ### Example: 404 Handler
126
249
 
127
- ```http
128
- 409 Conflict
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
- Example:
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.conflict(
135
- res,
136
- "Email Already Exists"
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
- Output:
280
+ ### Response
141
281
 
142
282
  ```json
143
283
  {
144
284
  "success": false,
145
- "message": "Email Already Exists",
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
- # internalError()
316
+ ## validationError()
153
317
 
154
- Status Code:
318
+ Use when request validation fails. This helper is specifically designed for detailed validation errors.
155
319
 
156
- ```http
157
- 500 Internal Server Error
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
- Example:
344
+ ### Syntax
161
345
 
346
+ **Middleware API:**
162
347
  ```js
163
- response.internalError(
164
- res,
165
- "Something Went Wrong"
166
- );
348
+ res.validationError(errors, message)
167
349
  ```
168
350
 
169
- Output:
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": "Something Went Wrong",
175
- "errors": null
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
- # validationError()
436
+ ## internalError()
182
437
 
183
- Used when user input validation fails.
438
+ Use for unexpected server errors.
184
439
 
185
- Example:
440
+ ### Using Middleware
186
441
 
187
442
  ```js
188
- response.validationError(
189
- res,
190
- {
191
- email: "Email is required",
192
- password: "Password is required"
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
- Output:
455
+ ### Response
198
456
 
199
457
  ```json
200
458
  {
201
459
  "success": false,
202
- "message": "Validation Failed",
203
- "errors": {
204
- "email": "Email is required",
205
- "password": "Password is required"
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
- # Best Practices
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
- Use:
699
+ ## Related
215
700
 
216
- * badRequest() for invalid requests
217
- * unauthorized() for invalid authentication
218
- * forbidden() for permission issues
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)