node-responder 1.0.0 → 1.2.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/README.md +458 -82
- package/package.json +2 -2
- package/src/index.js +235 -93
- package/types/index.d.ts +59 -27
package/README.md
CHANGED
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
> Modern, standardized API response middleware for Express.js
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/node-responder)
|
|
6
|
+
[](https://www.npmjs.com/package/node-responder)
|
|
6
7
|
[](LICENSE)
|
|
7
8
|
[](types/index.d.ts)
|
|
8
9
|
[]()
|
|
9
10
|
|
|
10
|
-
Stop writing repetitive `res.status(200).json({ success: true, data: ... })` in every route.
|
|
11
|
+
Stop writing repetitive `res.status(200).json({ success: true, data: ... })` in every route.
|
|
12
|
+
`node-responder` adds clean, consistent response helpers directly to Express's `res` object.
|
|
11
13
|
|
|
12
14
|
---
|
|
13
15
|
|
|
@@ -16,9 +18,10 @@ Stop writing repetitive `res.status(200).json({ success: true, data: ... })` in
|
|
|
16
18
|
- ✅ **Zero dependencies** — only Express as a peer dependency
|
|
17
19
|
- ✅ **TypeScript support** — full type definitions included
|
|
18
20
|
- ✅ **ESM + CommonJS** — works with both `require` and `import`
|
|
21
|
+
- ✅ **Request Logger** — logs method, URL, status, and response time to terminal
|
|
19
22
|
- ✅ **Pagination built-in** — `res.paginate()` with full meta
|
|
23
|
+
- ✅ **asyncHandler** — write async routes without try-catch boilerplate
|
|
20
24
|
- ✅ **Consistent format** — every response follows the same structure
|
|
21
|
-
- ✅ **Shorthand methods** — `res.ok()`, `res.notFound()`, `res.unauthorized()` and more
|
|
22
25
|
- ✅ **Node.js 14+** supported
|
|
23
26
|
|
|
24
27
|
---
|
|
@@ -38,15 +41,14 @@ const express = require("express");
|
|
|
38
41
|
const responder = require("node-responder");
|
|
39
42
|
|
|
40
43
|
const app = express();
|
|
44
|
+
app.use(express.json());
|
|
41
45
|
|
|
42
46
|
// Apply middleware globally
|
|
43
47
|
app.use(responder());
|
|
44
48
|
|
|
45
49
|
app.get("/user/:id", async (req, res) => {
|
|
46
50
|
const user = await User.findById(req.params.id);
|
|
47
|
-
|
|
48
51
|
if (!user) return res.notFound("User not found");
|
|
49
|
-
|
|
50
52
|
return res.ok(user);
|
|
51
53
|
});
|
|
52
54
|
|
|
@@ -57,23 +59,23 @@ app.listen(3000);
|
|
|
57
59
|
|
|
58
60
|
## 📋 Response Format
|
|
59
61
|
|
|
60
|
-
|
|
62
|
+
Every response follows the same consistent structure:
|
|
61
63
|
|
|
62
|
-
|
|
64
|
+
### Success Response
|
|
63
65
|
|
|
64
66
|
```json
|
|
65
67
|
{
|
|
66
68
|
"success": true,
|
|
67
69
|
"message": "Success",
|
|
68
|
-
"data": {
|
|
70
|
+
"data": { "id": 1, "name": "John" },
|
|
69
71
|
"meta": {
|
|
70
|
-
"timestamp": "
|
|
72
|
+
"timestamp": "2025-01-15T10:30:00.000Z",
|
|
71
73
|
"statusCode": 200
|
|
72
74
|
}
|
|
73
75
|
}
|
|
74
76
|
```
|
|
75
77
|
|
|
76
|
-
|
|
78
|
+
### Error Response
|
|
77
79
|
|
|
78
80
|
```json
|
|
79
81
|
{
|
|
@@ -82,21 +84,21 @@ All responses follow this consistent structure:
|
|
|
82
84
|
"data": null,
|
|
83
85
|
"errors": null,
|
|
84
86
|
"meta": {
|
|
85
|
-
"timestamp": "
|
|
87
|
+
"timestamp": "2025-01-15T10:30:00.000Z",
|
|
86
88
|
"statusCode": 404
|
|
87
89
|
}
|
|
88
90
|
}
|
|
89
91
|
```
|
|
90
92
|
|
|
91
|
-
|
|
93
|
+
### Paginated Response
|
|
92
94
|
|
|
93
95
|
```json
|
|
94
96
|
{
|
|
95
97
|
"success": true,
|
|
96
98
|
"message": "Users fetched",
|
|
97
|
-
"data": [
|
|
99
|
+
"data": [{ "id": 1 }, { "id": 2 }],
|
|
98
100
|
"meta": {
|
|
99
|
-
"timestamp": "
|
|
101
|
+
"timestamp": "2025-01-15T10:30:00.000Z",
|
|
100
102
|
"statusCode": 200,
|
|
101
103
|
"pagination": {
|
|
102
104
|
"page": 1,
|
|
@@ -112,121 +114,468 @@ All responses follow this consistent structure:
|
|
|
112
114
|
|
|
113
115
|
---
|
|
114
116
|
|
|
115
|
-
##
|
|
116
|
-
|
|
117
|
-
### Middleware Setup
|
|
117
|
+
## ⚙️ Middleware Setup
|
|
118
118
|
|
|
119
119
|
```js
|
|
120
120
|
const responder = require("node-responder");
|
|
121
121
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
122
|
+
// Apply to all routes
|
|
123
|
+
app.use(responder());
|
|
124
|
+
|
|
125
|
+
// With logger enabled
|
|
126
|
+
app.use(responder({ logger: true }));
|
|
127
|
+
|
|
128
|
+
// Apply to a specific router only
|
|
129
|
+
router.use(responder());
|
|
125
130
|
```
|
|
126
131
|
|
|
132
|
+
### Options
|
|
133
|
+
|
|
134
|
+
| Option | Type | Default | Description |
|
|
135
|
+
| -------- | --------- | ------- | ------------------------------------------------------------------------- |
|
|
136
|
+
| `logger` | `boolean` | `false` | Logs each request to terminal with method, URL, status, and response time |
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## 📖 API Reference
|
|
141
|
+
|
|
142
|
+
### Quick Reference
|
|
143
|
+
|
|
144
|
+
| Method | Status | When to use |
|
|
145
|
+
| -------------------------------------------- | ------ | ------------------------------------------ |
|
|
146
|
+
| `res.ok(data?, message?)` | 200 | Successful GET request |
|
|
147
|
+
| `res.created(data?, message?)` | 201 | New resource created |
|
|
148
|
+
| `res.noContent()` | 204 | Delete or update with no data to return |
|
|
149
|
+
| `res.success(data?, message?, statusCode?)` | custom | Custom success status code |
|
|
150
|
+
| `res.badRequest(message?, errors?)` | 400 | Validation failed |
|
|
151
|
+
| `res.unauthorized(message?)` | 401 | Not logged in or no token |
|
|
152
|
+
| `res.forbidden(message?)` | 403 | Logged in but no permission |
|
|
153
|
+
| `res.notFound(message?)` | 404 | Resource does not exist |
|
|
154
|
+
| `res.conflict(message?)` | 409 | Duplicate data (e.g. email already exists) |
|
|
155
|
+
| `res.unprocessable(message?, errors?)` | 422 | Business logic validation failed |
|
|
156
|
+
| `res.tooManyRequests(message?, retryAfter?)` | 429 | Rate limit exceeded |
|
|
157
|
+
| `res.serverError(message?)` | 500 | Unexpected server-side error |
|
|
158
|
+
| `res.error(message?, statusCode?, errors?)` | custom | Custom error status code |
|
|
159
|
+
| `res.paginate(data, message?, pagination?)` | 200 | Paginated list response |
|
|
160
|
+
|
|
127
161
|
---
|
|
128
162
|
|
|
129
163
|
### ✅ Success Methods
|
|
130
164
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
| `res.noContent()` | 204 | No content |
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
#### `res.ok(data?, message?)`
|
|
168
|
+
|
|
169
|
+
**Status: 200** — Use for standard successful GET requests.
|
|
137
170
|
|
|
138
171
|
```js
|
|
139
|
-
|
|
140
|
-
res.
|
|
141
|
-
|
|
172
|
+
// With data only
|
|
173
|
+
res.ok({ id: 1, name: "John" });
|
|
174
|
+
|
|
175
|
+
// With data and custom message
|
|
176
|
+
res.ok({ id: 1, name: "John" }, "User fetched successfully");
|
|
177
|
+
|
|
178
|
+
// Response:
|
|
179
|
+
// { success: true, message: "User fetched successfully", data: { id: 1, name: "John" }, meta: { statusCode: 200, ... } }
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
#### `res.created(data?, message?)`
|
|
185
|
+
|
|
186
|
+
**Status: 201** — Use when a new resource has been successfully created.
|
|
187
|
+
|
|
188
|
+
```js
|
|
189
|
+
const user = await User.create({ name: "John", email: "john@example.com" });
|
|
190
|
+
|
|
191
|
+
res.created(user);
|
|
192
|
+
|
|
193
|
+
// With custom message
|
|
194
|
+
res.created(user, "Account created successfully");
|
|
195
|
+
|
|
196
|
+
// Response:
|
|
197
|
+
// { success: true, message: "Created successfully", data: { ...user }, meta: { statusCode: 201, ... } }
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
#### `res.noContent()`
|
|
203
|
+
|
|
204
|
+
**Status: 204** — Use after a successful delete or update when no data needs to be returned.
|
|
205
|
+
|
|
206
|
+
```js
|
|
207
|
+
await User.findByIdAndDelete(req.params.id);
|
|
208
|
+
res.noContent();
|
|
209
|
+
|
|
210
|
+
// Response: empty body (HTTP 204 No Content)
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
#### `res.success(data?, message?, statusCode?)`
|
|
216
|
+
|
|
217
|
+
**Status: custom** — Use when you need a custom success status code.
|
|
218
|
+
|
|
219
|
+
```js
|
|
220
|
+
res.success({ accepted: true }, "Request accepted", 202);
|
|
221
|
+
|
|
222
|
+
// Response:
|
|
223
|
+
// { success: true, message: "Request accepted", data: { accepted: true }, meta: { statusCode: 202, ... } }
|
|
142
224
|
```
|
|
143
225
|
|
|
144
226
|
---
|
|
145
227
|
|
|
146
228
|
### ❌ Error Methods
|
|
147
229
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
#### `res.badRequest(message?, errors?)`
|
|
233
|
+
|
|
234
|
+
**Status: 400** — Use when the request is malformed or validation fails.
|
|
235
|
+
|
|
236
|
+
```js
|
|
237
|
+
// Simple message
|
|
238
|
+
res.badRequest("Name is required");
|
|
239
|
+
|
|
240
|
+
// With validation errors object
|
|
241
|
+
res.badRequest("Validation failed", {
|
|
242
|
+
name: "Name is required",
|
|
243
|
+
email: "Invalid email format",
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// Response:
|
|
247
|
+
// { success: false, message: "Validation failed", data: null, errors: { name: "...", email: "..." }, meta: { statusCode: 400, ... } }
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
#### `res.unauthorized(message?)`
|
|
253
|
+
|
|
254
|
+
**Status: 401** — Use when the user is not authenticated (no token or invalid token).
|
|
255
|
+
|
|
256
|
+
```js
|
|
257
|
+
// Default message
|
|
258
|
+
res.unauthorized();
|
|
259
|
+
|
|
260
|
+
// Custom message
|
|
261
|
+
res.unauthorized("Please login to continue");
|
|
262
|
+
|
|
263
|
+
// Response:
|
|
264
|
+
// { success: false, message: "Unauthorized", data: null, errors: null, meta: { statusCode: 401, ... } }
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
#### `res.forbidden(message?)`
|
|
270
|
+
|
|
271
|
+
**Status: 403** — Use when the user is authenticated but does not have permission.
|
|
158
272
|
|
|
159
273
|
```js
|
|
160
|
-
|
|
161
|
-
res.
|
|
162
|
-
|
|
274
|
+
// Default message
|
|
275
|
+
res.forbidden();
|
|
276
|
+
|
|
277
|
+
// Custom message
|
|
278
|
+
res.forbidden("You do not have permission to access this resource");
|
|
279
|
+
|
|
280
|
+
// Response:
|
|
281
|
+
// { success: false, message: "Forbidden", data: null, errors: null, meta: { statusCode: 403, ... } }
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
---
|
|
285
|
+
|
|
286
|
+
#### `res.notFound(message?)`
|
|
287
|
+
|
|
288
|
+
**Status: 404** — Use when a requested resource does not exist.
|
|
289
|
+
|
|
290
|
+
```js
|
|
291
|
+
const user = await User.findById(req.params.id);
|
|
292
|
+
|
|
293
|
+
if (!user) {
|
|
294
|
+
return res.notFound("User not found");
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Response:
|
|
298
|
+
// { success: false, message: "User not found", data: null, errors: null, meta: { statusCode: 404, ... } }
|
|
163
299
|
```
|
|
164
300
|
|
|
165
301
|
---
|
|
166
302
|
|
|
167
|
-
|
|
303
|
+
#### `res.conflict(message?)`
|
|
304
|
+
|
|
305
|
+
**Status: 409** — Use when the request conflicts with existing data (e.g. duplicate email).
|
|
168
306
|
|
|
169
307
|
```js
|
|
170
|
-
const
|
|
171
|
-
|
|
308
|
+
const exists = await User.findOne({ email: req.body.email });
|
|
309
|
+
|
|
310
|
+
if (exists) {
|
|
311
|
+
return res.conflict("Email already registered");
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Response:
|
|
315
|
+
// { success: false, message: "Email already registered", data: null, errors: null, meta: { statusCode: 409, ... } }
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
#### `res.unprocessable(message?, errors?)`
|
|
321
|
+
|
|
322
|
+
**Status: 422** — Use when the data format is correct but business logic validation fails.
|
|
172
323
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
324
|
+
```js
|
|
325
|
+
res.unprocessable("Cannot process this request", {
|
|
326
|
+
age: "Must be at least 18 years old",
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
// Response:
|
|
330
|
+
// { success: false, message: "Cannot process this request", data: null, errors: { age: "..." }, meta: { statusCode: 422, ... } }
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
#### `res.tooManyRequests(message?, retryAfter?)`
|
|
336
|
+
|
|
337
|
+
**Status: 429** — Use when a client exceeds the rate limit.
|
|
338
|
+
|
|
339
|
+
```js
|
|
340
|
+
// Basic usage
|
|
341
|
+
res.tooManyRequests("Too many requests, please slow down");
|
|
342
|
+
|
|
343
|
+
// With retryAfter in seconds — sets the Retry-After header automatically
|
|
344
|
+
res.tooManyRequests("Rate limit exceeded", 60);
|
|
345
|
+
|
|
346
|
+
// Response:
|
|
347
|
+
// Header: Retry-After: 60
|
|
348
|
+
// { success: false, message: "Rate limit exceeded", data: null, errors: null, meta: { statusCode: 429, ... } }
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
---
|
|
352
|
+
|
|
353
|
+
#### `res.serverError(message?)`
|
|
354
|
+
|
|
355
|
+
**Status: 500** — Use when an unexpected server-side error occurs.
|
|
356
|
+
|
|
357
|
+
```js
|
|
358
|
+
try {
|
|
359
|
+
await someRiskyOperation();
|
|
360
|
+
} catch (err) {
|
|
361
|
+
console.error(err);
|
|
362
|
+
return res.serverError("Something went wrong, please try again later");
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Response:
|
|
366
|
+
// { success: false, message: "Something went wrong, please try again later", data: null, errors: null, meta: { statusCode: 500, ... } }
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
#### `res.error(message?, statusCode?, errors?)`
|
|
372
|
+
|
|
373
|
+
**Status: custom** — Use when you need a custom error status code.
|
|
374
|
+
|
|
375
|
+
```js
|
|
376
|
+
res.error("Gone", 410);
|
|
377
|
+
res.error("Validation failed", 400, { field: "required" });
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
### 📄 Pagination — `res.paginate(data, message?, pagination?)`
|
|
383
|
+
|
|
384
|
+
Use for returning paginated lists with full pagination metadata.
|
|
385
|
+
|
|
386
|
+
```js
|
|
387
|
+
router.get("/users", async (req, res) => {
|
|
388
|
+
const page = parseInt(req.query.page) || 1;
|
|
389
|
+
const limit = parseInt(req.query.limit) || 10;
|
|
390
|
+
const skip = (page - 1) * limit;
|
|
391
|
+
|
|
392
|
+
const [users, total] = await Promise.all([
|
|
393
|
+
User.find().skip(skip).limit(limit),
|
|
394
|
+
User.countDocuments(),
|
|
395
|
+
]);
|
|
396
|
+
|
|
397
|
+
return res.paginate(users, "Users fetched", { page, limit, total });
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
// Response:
|
|
401
|
+
// {
|
|
402
|
+
// success: true,
|
|
403
|
+
// message: "Users fetched",
|
|
404
|
+
// data: [...users],
|
|
405
|
+
// meta: {
|
|
406
|
+
// timestamp: "...",
|
|
407
|
+
// statusCode: 200,
|
|
408
|
+
// pagination: {
|
|
409
|
+
// page: 1, limit: 10, total: 100,
|
|
410
|
+
// totalPages: 10,
|
|
411
|
+
// hasNextPage: true,
|
|
412
|
+
// hasPrevPage: false
|
|
413
|
+
// }
|
|
414
|
+
// }
|
|
415
|
+
// }
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
| Pagination Field | Type | Description |
|
|
419
|
+
| ---------------- | -------- | ------------------------- |
|
|
420
|
+
| `page` | `number` | Current page number |
|
|
421
|
+
| `limit` | `number` | Number of items per page |
|
|
422
|
+
| `total` | `number` | Total number of documents |
|
|
423
|
+
|
|
424
|
+
---
|
|
425
|
+
|
|
426
|
+
### ⚡ asyncHandler
|
|
427
|
+
|
|
428
|
+
Eliminates try-catch boilerplate from every async route.
|
|
429
|
+
Errors are automatically forwarded to Express's `next(err)`.
|
|
430
|
+
|
|
431
|
+
```js
|
|
432
|
+
const { asyncHandler } = require("node-responder");
|
|
433
|
+
|
|
434
|
+
// ❌ Before — repetitive try-catch in every route:
|
|
435
|
+
router.get("/users", async (req, res) => {
|
|
436
|
+
try {
|
|
437
|
+
const users = await User.find();
|
|
438
|
+
res.ok(users);
|
|
439
|
+
} catch (err) {
|
|
440
|
+
res.serverError(err.message);
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
// ✅ After — clean and concise with asyncHandler:
|
|
445
|
+
router.get(
|
|
446
|
+
"/users",
|
|
447
|
+
asyncHandler(async (req, res) => {
|
|
448
|
+
const users = await User.find(); // errors automatically go to next(err)
|
|
449
|
+
res.ok(users);
|
|
450
|
+
}),
|
|
451
|
+
);
|
|
452
|
+
|
|
453
|
+
// Catch all errors in one global error handler:
|
|
454
|
+
app.use((err, req, res, next) => {
|
|
455
|
+
console.error(err);
|
|
456
|
+
res.serverError(err.message);
|
|
177
457
|
});
|
|
178
458
|
```
|
|
179
459
|
|
|
180
460
|
---
|
|
181
461
|
|
|
182
|
-
|
|
462
|
+
### 📝 Logger — `{ logger: true }`
|
|
463
|
+
|
|
464
|
+
Logs every incoming request to the terminal with method, URL, status code, and response time.
|
|
465
|
+
|
|
466
|
+
```js
|
|
467
|
+
app.use(responder({ logger: true }));
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
Terminal output:
|
|
471
|
+
|
|
472
|
+
```
|
|
473
|
+
GET /api/users 200 23ms ✔
|
|
474
|
+
POST /api/users 201 45ms ✔
|
|
475
|
+
GET /api/users/abc123 404 12ms ✖
|
|
476
|
+
POST /api/auth/login 401 8ms ✖
|
|
477
|
+
DELETE /api/products/999 500 5ms ✖
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
**Tip:** Enable logger in development, disable in production:
|
|
481
|
+
|
|
482
|
+
```js
|
|
483
|
+
app.use(
|
|
484
|
+
responder({
|
|
485
|
+
logger: process.env.NODE_ENV !== "production",
|
|
486
|
+
}),
|
|
487
|
+
);
|
|
488
|
+
```
|
|
489
|
+
|
|
490
|
+
---
|
|
491
|
+
|
|
492
|
+
## 🔧 Real-World MERN Example
|
|
183
493
|
|
|
184
494
|
```js
|
|
185
495
|
const express = require("express");
|
|
186
496
|
const responder = require("node-responder");
|
|
187
|
-
const
|
|
497
|
+
const { asyncHandler } = require("node-responder");
|
|
188
498
|
|
|
189
|
-
router.
|
|
499
|
+
const router = express.Router();
|
|
500
|
+
router.use(responder({ logger: true }));
|
|
190
501
|
|
|
191
|
-
// GET
|
|
192
|
-
router.get(
|
|
193
|
-
|
|
502
|
+
// GET /api/users?page=1&limit=10
|
|
503
|
+
router.get(
|
|
504
|
+
"/",
|
|
505
|
+
asyncHandler(async (req, res) => {
|
|
194
506
|
const page = parseInt(req.query.page) || 1;
|
|
195
507
|
const limit = parseInt(req.query.limit) || 10;
|
|
196
508
|
const skip = (page - 1) * limit;
|
|
197
509
|
|
|
198
510
|
const [users, total] = await Promise.all([
|
|
199
|
-
User.find().skip(skip).limit(limit),
|
|
511
|
+
User.find().skip(skip).limit(limit).select("-password"),
|
|
200
512
|
User.countDocuments(),
|
|
201
513
|
]);
|
|
202
514
|
|
|
203
515
|
return res.paginate(users, "Users fetched", { page, limit, total });
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
516
|
+
}),
|
|
517
|
+
);
|
|
518
|
+
|
|
519
|
+
// GET /api/users/:id
|
|
520
|
+
router.get(
|
|
521
|
+
"/:id",
|
|
522
|
+
asyncHandler(async (req, res) => {
|
|
523
|
+
const user = await User.findById(req.params.id).select("-password");
|
|
524
|
+
if (!user) return res.notFound("User not found");
|
|
525
|
+
return res.ok(user, "User fetched");
|
|
526
|
+
}),
|
|
527
|
+
);
|
|
528
|
+
|
|
529
|
+
// POST /api/users
|
|
530
|
+
router.post(
|
|
531
|
+
"/",
|
|
532
|
+
asyncHandler(async (req, res) => {
|
|
533
|
+
const { name, email, password } = req.body;
|
|
534
|
+
|
|
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.badRequest("Validation failed", errors);
|
|
219
542
|
}
|
|
220
543
|
|
|
221
544
|
const exists = await User.findOne({ email });
|
|
222
545
|
if (exists) return res.conflict("Email already registered");
|
|
223
546
|
|
|
224
|
-
const user = await User.create({ name, email });
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
547
|
+
const user = await User.create({ name, email, password });
|
|
548
|
+
|
|
549
|
+
return res.created(
|
|
550
|
+
{ id: user._id, name: user.name, email: user.email },
|
|
551
|
+
"Account created successfully",
|
|
552
|
+
);
|
|
553
|
+
}),
|
|
554
|
+
);
|
|
555
|
+
|
|
556
|
+
// PUT /api/users/:id
|
|
557
|
+
router.put(
|
|
558
|
+
"/:id",
|
|
559
|
+
asyncHandler(async (req, res) => {
|
|
560
|
+
const user = await User.findByIdAndUpdate(req.params.id, req.body, {
|
|
561
|
+
new: true,
|
|
562
|
+
});
|
|
563
|
+
if (!user) return res.notFound("User not found");
|
|
564
|
+
return res.ok(user, "User updated successfully");
|
|
565
|
+
}),
|
|
566
|
+
);
|
|
567
|
+
|
|
568
|
+
// DELETE /api/users/:id
|
|
569
|
+
router.delete(
|
|
570
|
+
"/:id",
|
|
571
|
+
asyncHandler(async (req, res) => {
|
|
572
|
+
const user = await User.findByIdAndDelete(req.params.id);
|
|
573
|
+
if (!user) return res.notFound("User not found");
|
|
574
|
+
return res.noContent();
|
|
575
|
+
}),
|
|
576
|
+
);
|
|
577
|
+
|
|
578
|
+
module.exports = router;
|
|
230
579
|
```
|
|
231
580
|
|
|
232
581
|
---
|
|
@@ -235,19 +584,46 @@ router.post("/", async (req, res) => {
|
|
|
235
584
|
|
|
236
585
|
```ts
|
|
237
586
|
import express from "express";
|
|
238
|
-
import responder from "node-responder";
|
|
587
|
+
import responder, { asyncHandler } from "node-responder";
|
|
239
588
|
|
|
240
589
|
const app = express();
|
|
241
|
-
app.use(
|
|
590
|
+
app.use(express.json());
|
|
591
|
+
app.use(responder({ logger: true }));
|
|
592
|
+
|
|
593
|
+
app.get(
|
|
594
|
+
"/users",
|
|
595
|
+
asyncHandler(async (req, res) => {
|
|
596
|
+
const users = await User.find();
|
|
597
|
+
res.ok(users, "Users fetched");
|
|
598
|
+
}),
|
|
599
|
+
);
|
|
600
|
+
|
|
601
|
+
// Global error handler
|
|
602
|
+
app.use(
|
|
603
|
+
(
|
|
604
|
+
err: Error,
|
|
605
|
+
req: express.Request,
|
|
606
|
+
res: express.Response,
|
|
607
|
+
next: express.NextFunction,
|
|
608
|
+
) => {
|
|
609
|
+
console.error(err);
|
|
610
|
+
res.serverError(err.message);
|
|
611
|
+
},
|
|
612
|
+
);
|
|
242
613
|
|
|
243
|
-
app.
|
|
244
|
-
const users = await User.find();
|
|
245
|
-
res.ok(users, "Users fetched");
|
|
246
|
-
});
|
|
614
|
+
app.listen(3000);
|
|
247
615
|
```
|
|
248
616
|
|
|
249
617
|
---
|
|
250
618
|
|
|
619
|
+
## 🔗 Links
|
|
620
|
+
|
|
621
|
+
- [npm](https://www.npmjs.com/package/node-responder)
|
|
622
|
+
- [GitHub](https://github.com/hammadsadi/node-responder)
|
|
623
|
+
- [Report an Issue](https://github.com/hammadsadi/node-responder/issues)
|
|
624
|
+
|
|
625
|
+
---
|
|
626
|
+
|
|
251
627
|
## 📄 License
|
|
252
628
|
|
|
253
629
|
MIT © [Hammad Sadi](https://github.com/hammadsadi)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-responder",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "Modern, standardized API response middleware for Express.js — with TypeScript support, pagination, and shorthand methods.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"types": "types/index.d.ts",
|
|
@@ -56,4 +56,4 @@
|
|
|
56
56
|
"engines": {
|
|
57
57
|
"node": ">=14.0.0"
|
|
58
58
|
}
|
|
59
|
-
}
|
|
59
|
+
}
|
package/src/index.js
CHANGED
|
@@ -3,140 +3,282 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* node-responder
|
|
5
5
|
* Standardized, modern API response middleware for Express.js
|
|
6
|
-
* @
|
|
6
|
+
* @version 1.1.0
|
|
7
7
|
* @license MIT
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
10
|
+
// ANSI Colors
|
|
11
|
+
|
|
12
|
+
const COLORS = {
|
|
13
|
+
reset: "\x1b[0m",
|
|
14
|
+
dim: "\x1b[2m",
|
|
15
|
+
bold: "\x1b[1m",
|
|
16
|
+
green: "\x1b[32m",
|
|
17
|
+
yellow: "\x1b[33m",
|
|
18
|
+
red: "\x1b[31m",
|
|
19
|
+
cyan: "\x1b[36m",
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const isTTY = () => process.stdout && process.stdout.isTTY === true;
|
|
23
|
+
|
|
24
|
+
const colorize = (str, color) =>
|
|
25
|
+
isTTY() ? `${color}${str}${COLORS.reset}` : str;
|
|
26
|
+
|
|
27
|
+
const colorStatus = (code) => {
|
|
28
|
+
if (code >= 500) return colorize(code, COLORS.red);
|
|
29
|
+
if (code >= 400) return colorize(code, COLORS.yellow);
|
|
30
|
+
if (code >= 300) return colorize(code, COLORS.cyan);
|
|
31
|
+
return colorize(code, COLORS.green);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const colorMethod = (method) => {
|
|
35
|
+
const m = (method || "UNKNOWN").toUpperCase().padEnd(7);
|
|
36
|
+
return colorize(m, COLORS.cyan);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const logRequest = (req, statusCode, startTime) => {
|
|
40
|
+
try {
|
|
41
|
+
const ms = Date.now() - startTime;
|
|
42
|
+
const method = colorMethod(req.method);
|
|
43
|
+
const url = (req.originalUrl || req.url || "/").padEnd(35);
|
|
44
|
+
const status = colorStatus(statusCode);
|
|
45
|
+
const time = colorize(`${ms}ms`, COLORS.dim);
|
|
46
|
+
const icon =
|
|
47
|
+
statusCode >= 400
|
|
48
|
+
? colorize("✖", COLORS.red)
|
|
49
|
+
: colorize("✔", COLORS.green);
|
|
50
|
+
process.stdout.write(` ${method} ${url} ${status} ${time} ${icon}\n`);
|
|
51
|
+
} catch (_) {}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// Validation helpers
|
|
55
|
+
|
|
56
|
+
const safeMessage = (val, fallback) => {
|
|
57
|
+
if (val === undefined || val === null) return fallback;
|
|
58
|
+
return String(val);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const safeStatus = (val, fallback) => {
|
|
62
|
+
const n = Number(val);
|
|
63
|
+
return Number.isFinite(n) && n >= 100 && n <= 599 ? n : fallback;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// Core response builders
|
|
67
|
+
|
|
68
|
+
const successResponse = (res, data, message, statusCode) => {
|
|
69
|
+
const code = safeStatus(statusCode, 200);
|
|
70
|
+
const msg = safeMessage(message, "Success");
|
|
71
|
+
const body = data === undefined ? null : data;
|
|
72
|
+
|
|
73
|
+
return res.status(code).json({
|
|
20
74
|
success: true,
|
|
21
|
-
message,
|
|
22
|
-
data,
|
|
75
|
+
message: msg,
|
|
76
|
+
data: body,
|
|
23
77
|
meta: {
|
|
24
78
|
timestamp: new Date().toISOString(),
|
|
25
|
-
statusCode,
|
|
79
|
+
statusCode: code,
|
|
26
80
|
},
|
|
27
81
|
});
|
|
28
|
-
}
|
|
82
|
+
};
|
|
29
83
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
statusCode = 500,
|
|
37
|
-
errors = null,
|
|
38
|
-
) {
|
|
39
|
-
return res.status(statusCode).json({
|
|
84
|
+
const errorResponse = (res, message, statusCode, errors) => {
|
|
85
|
+
const code = safeStatus(statusCode, 500);
|
|
86
|
+
const msg = safeMessage(message, "Something went wrong");
|
|
87
|
+
const errs = errors === undefined || errors === null ? null : errors;
|
|
88
|
+
|
|
89
|
+
return res.status(code).json({
|
|
40
90
|
success: false,
|
|
41
|
-
message,
|
|
91
|
+
message: msg,
|
|
42
92
|
data: null,
|
|
43
|
-
errors,
|
|
93
|
+
errors: errs,
|
|
44
94
|
meta: {
|
|
45
95
|
timestamp: new Date().toISOString(),
|
|
46
|
-
statusCode,
|
|
96
|
+
statusCode: code,
|
|
47
97
|
},
|
|
48
98
|
});
|
|
49
|
-
}
|
|
99
|
+
};
|
|
50
100
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
res,
|
|
56
|
-
data = [],
|
|
57
|
-
message = "Success",
|
|
58
|
-
pagination = {},
|
|
59
|
-
) {
|
|
60
|
-
const { page = 1, limit = 10, total = 0 } = pagination;
|
|
101
|
+
const paginatedResponse = (res, data, message, pagination) => {
|
|
102
|
+
const arr = Array.isArray(data) ? data : [];
|
|
103
|
+
const msg = safeMessage(message, "Success");
|
|
104
|
+
const p = pagination && typeof pagination === "object" ? pagination : {};
|
|
61
105
|
|
|
62
|
-
const
|
|
106
|
+
const page = Math.max(1, parseInt(p.page, 10) || 1);
|
|
107
|
+
const limit = Math.max(1, parseInt(p.limit, 10) || 10);
|
|
108
|
+
const total = Math.max(0, parseInt(p.total, 10) || 0);
|
|
109
|
+
const totalPages = limit > 0 ? Math.ceil(total / limit) : 0;
|
|
63
110
|
|
|
64
111
|
return res.status(200).json({
|
|
65
112
|
success: true,
|
|
66
|
-
message,
|
|
67
|
-
data,
|
|
113
|
+
message: msg,
|
|
114
|
+
data: arr,
|
|
68
115
|
meta: {
|
|
69
116
|
timestamp: new Date().toISOString(),
|
|
70
117
|
statusCode: 200,
|
|
71
118
|
pagination: {
|
|
72
|
-
page
|
|
73
|
-
limit
|
|
74
|
-
total
|
|
119
|
+
page,
|
|
120
|
+
limit,
|
|
121
|
+
total,
|
|
75
122
|
totalPages,
|
|
76
123
|
hasNextPage: page < totalPages,
|
|
77
124
|
hasPrevPage: page > 1,
|
|
78
125
|
},
|
|
79
126
|
},
|
|
80
127
|
});
|
|
81
|
-
}
|
|
128
|
+
};
|
|
82
129
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
130
|
+
// asyncHandler
|
|
131
|
+
|
|
132
|
+
const asyncHandler = (fn) => {
|
|
133
|
+
if (typeof fn !== "function") {
|
|
134
|
+
throw new TypeError(
|
|
135
|
+
`[node-responder] asyncHandler expects a function, got "${typeof fn}"`,
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
return (req, res, next) => {
|
|
139
|
+
try {
|
|
140
|
+
const result = fn(req, res, next);
|
|
141
|
+
if (result && typeof result.catch === "function") {
|
|
142
|
+
result.catch(next);
|
|
143
|
+
}
|
|
144
|
+
} catch (err) {
|
|
145
|
+
next(err);
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// Middleware factory
|
|
151
|
+
|
|
152
|
+
const responder = (options) => {
|
|
153
|
+
const opts = options && typeof options === "object" ? options : {};
|
|
154
|
+
const logger = opts.logger === true;
|
|
155
|
+
|
|
156
|
+
if (process.env.NODE_ENV !== "production") {
|
|
157
|
+
const known = ["logger"];
|
|
158
|
+
for (const key of Object.keys(opts)) {
|
|
159
|
+
if (!known.includes(key)) {
|
|
160
|
+
process.stderr.write(
|
|
161
|
+
`[node-responder] Unknown option "${key}" — valid options: ${known.join(", ")}\n`,
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return (req, res, next) => {
|
|
168
|
+
const startTime = Date.now();
|
|
169
|
+
|
|
170
|
+
const log = (code) => {
|
|
171
|
+
if (logger) logRequest(req, code, startTime);
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
// 2xx Success
|
|
175
|
+
|
|
176
|
+
res.success = (data, message, statusCode) => {
|
|
177
|
+
const code = safeStatus(statusCode, 200);
|
|
178
|
+
log(code);
|
|
179
|
+
return successResponse(res, data, message, code);
|
|
95
180
|
};
|
|
96
181
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
statusCode = 500,
|
|
101
|
-
errors = null,
|
|
102
|
-
) {
|
|
103
|
-
return errorResponse(res, message, statusCode, errors);
|
|
182
|
+
res.ok = (data, message) => {
|
|
183
|
+
log(200);
|
|
184
|
+
return successResponse(res, data, safeMessage(message, "Success"), 200);
|
|
104
185
|
};
|
|
105
186
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
return
|
|
187
|
+
res.created = (data, message) => {
|
|
188
|
+
log(201);
|
|
189
|
+
return successResponse(
|
|
190
|
+
res,
|
|
191
|
+
data,
|
|
192
|
+
safeMessage(message, "Created successfully"),
|
|
193
|
+
201,
|
|
194
|
+
);
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
res.noContent = () => {
|
|
198
|
+
log(204);
|
|
199
|
+
return res.status(204).send();
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
// 4xx / 5xx Error
|
|
203
|
+
|
|
204
|
+
res.error = (message, statusCode, errors) => {
|
|
205
|
+
const code = safeStatus(statusCode, 500);
|
|
206
|
+
log(code);
|
|
207
|
+
return errorResponse(res, message, code, errors);
|
|
109
208
|
};
|
|
110
209
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
errorResponse(res, message,
|
|
210
|
+
res.badRequest = (message, errors) => {
|
|
211
|
+
log(400);
|
|
212
|
+
return errorResponse(
|
|
213
|
+
res,
|
|
214
|
+
safeMessage(message, "Bad request"),
|
|
215
|
+
400,
|
|
216
|
+
errors ?? null,
|
|
217
|
+
);
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
res.unauthorized = (message) => {
|
|
221
|
+
log(401);
|
|
222
|
+
return errorResponse(res, safeMessage(message, "Unauthorized"), 401);
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
res.forbidden = (message) => {
|
|
226
|
+
log(403);
|
|
227
|
+
return errorResponse(res, safeMessage(message, "Forbidden"), 403);
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
res.notFound = (message) => {
|
|
231
|
+
log(404);
|
|
232
|
+
return errorResponse(res, safeMessage(message, "Not found"), 404);
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
res.conflict = (message) => {
|
|
236
|
+
log(409);
|
|
237
|
+
return errorResponse(res, safeMessage(message, "Conflict"), 409);
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
res.unprocessable = (message, errors) => {
|
|
241
|
+
log(422);
|
|
242
|
+
return errorResponse(
|
|
243
|
+
res,
|
|
244
|
+
safeMessage(message, "Unprocessable entity"),
|
|
245
|
+
422,
|
|
246
|
+
errors ?? null,
|
|
247
|
+
);
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
res.tooManyRequests = (message, retryAfter) => {
|
|
251
|
+
if (retryAfter != null) res.set("Retry-After", String(retryAfter));
|
|
252
|
+
log(429);
|
|
253
|
+
return errorResponse(res, safeMessage(message, "Too many requests"), 429);
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
res.serverError = (message) => {
|
|
257
|
+
log(500);
|
|
258
|
+
return errorResponse(
|
|
259
|
+
res,
|
|
260
|
+
safeMessage(message, "Internal server error"),
|
|
261
|
+
500,
|
|
262
|
+
);
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
// Pagination
|
|
266
|
+
|
|
267
|
+
res.paginate = (data, message, pagination) => {
|
|
268
|
+
log(200);
|
|
269
|
+
return paginatedResponse(res, data, message, pagination);
|
|
270
|
+
};
|
|
129
271
|
|
|
130
272
|
next();
|
|
131
273
|
};
|
|
132
|
-
}
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
// Exports
|
|
133
277
|
|
|
134
|
-
|
|
135
|
-
module.exports =
|
|
136
|
-
module.exports.
|
|
278
|
+
module.exports = responder;
|
|
279
|
+
module.exports.default = responder;
|
|
280
|
+
module.exports.responder = responder;
|
|
281
|
+
module.exports.asyncHandler = asyncHandler;
|
|
137
282
|
module.exports.successResponse = successResponse;
|
|
138
283
|
module.exports.errorResponse = errorResponse;
|
|
139
284
|
module.exports.paginatedResponse = paginatedResponse;
|
|
140
|
-
|
|
141
|
-
// ESM default export support
|
|
142
|
-
module.exports.default = apiResponse;
|
package/types/index.d.ts
CHANGED
|
@@ -1,9 +1,21 @@
|
|
|
1
|
-
import { Request, Response, NextFunction
|
|
1
|
+
import { RequestHandler, Request, Response, NextFunction } from "express";
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
// Options
|
|
4
|
+
|
|
5
|
+
export interface ResponderOptions {
|
|
6
|
+
/**
|
|
7
|
+
* When true, logs every request to stdout with method, URL, status, and response time.
|
|
8
|
+
* @default false
|
|
9
|
+
*/
|
|
10
|
+
logger?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Pagination
|
|
14
|
+
|
|
15
|
+
export interface PaginationInput {
|
|
16
|
+
page?: number | string;
|
|
17
|
+
limit?: number | string;
|
|
18
|
+
total?: number | string;
|
|
7
19
|
}
|
|
8
20
|
|
|
9
21
|
export interface PaginationMeta {
|
|
@@ -15,13 +27,15 @@ export interface PaginationMeta {
|
|
|
15
27
|
hasPrevPage: boolean;
|
|
16
28
|
}
|
|
17
29
|
|
|
30
|
+
// Response shapes
|
|
31
|
+
|
|
18
32
|
export interface ApiMeta {
|
|
19
33
|
timestamp: string;
|
|
20
34
|
statusCode: number;
|
|
21
35
|
pagination?: PaginationMeta;
|
|
22
36
|
}
|
|
23
37
|
|
|
24
|
-
export interface ApiSuccessResponse<T =
|
|
38
|
+
export interface ApiSuccessResponse<T = unknown> {
|
|
25
39
|
success: true;
|
|
26
40
|
message: string;
|
|
27
41
|
data: T;
|
|
@@ -32,40 +46,42 @@ export interface ApiErrorResponse {
|
|
|
32
46
|
success: false;
|
|
33
47
|
message: string;
|
|
34
48
|
data: null;
|
|
35
|
-
errors:
|
|
49
|
+
errors: unknown | null;
|
|
36
50
|
meta: ApiMeta;
|
|
37
51
|
}
|
|
38
52
|
|
|
53
|
+
// Express augmentation
|
|
54
|
+
|
|
39
55
|
declare global {
|
|
40
56
|
namespace Express {
|
|
41
57
|
interface Response {
|
|
42
|
-
|
|
43
|
-
success
|
|
58
|
+
// Generic
|
|
59
|
+
/** Send a success response with optional data, message, and status code */
|
|
60
|
+
success<T = unknown>(
|
|
44
61
|
data?: T,
|
|
45
62
|
message?: string,
|
|
46
63
|
statusCode?: number,
|
|
47
64
|
): Response;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
/** Send a paginated response */
|
|
53
|
-
paginate<T = any>(
|
|
65
|
+
/** Send an error response with optional message, status code, and errors */
|
|
66
|
+
error(message?: string, statusCode?: number, errors?: unknown): Response;
|
|
67
|
+
/** Send a paginated success response */
|
|
68
|
+
paginate<T = unknown>(
|
|
54
69
|
data?: T[],
|
|
55
70
|
message?: string,
|
|
56
|
-
pagination?:
|
|
71
|
+
pagination?: PaginationInput,
|
|
57
72
|
): Response;
|
|
58
73
|
|
|
59
|
-
//
|
|
74
|
+
// 2xx
|
|
60
75
|
/** 200 OK */
|
|
61
|
-
ok<T =
|
|
76
|
+
ok<T = unknown>(data?: T, message?: string): Response;
|
|
62
77
|
/** 201 Created */
|
|
63
|
-
created<T =
|
|
78
|
+
created<T = unknown>(data?: T, message?: string): Response;
|
|
64
79
|
/** 204 No Content */
|
|
65
80
|
noContent(): Response;
|
|
66
81
|
|
|
82
|
+
// 4xx
|
|
67
83
|
/** 400 Bad Request */
|
|
68
|
-
badRequest(message?: string, errors?:
|
|
84
|
+
badRequest(message?: string, errors?: unknown): Response;
|
|
69
85
|
/** 401 Unauthorized */
|
|
70
86
|
unauthorized(message?: string): Response;
|
|
71
87
|
/** 403 Forbidden */
|
|
@@ -75,18 +91,34 @@ declare global {
|
|
|
75
91
|
/** 409 Conflict */
|
|
76
92
|
conflict(message?: string): Response;
|
|
77
93
|
/** 422 Unprocessable Entity */
|
|
78
|
-
unprocessable(message?: string, errors?:
|
|
94
|
+
unprocessable(message?: string, errors?: unknown): Response;
|
|
95
|
+
/** 429 Too Many Requests — optionally sets Retry-After header */
|
|
96
|
+
tooManyRequests(message?: string, retryAfter?: number | string): Response;
|
|
97
|
+
|
|
98
|
+
// 5xx
|
|
79
99
|
/** 500 Internal Server Error */
|
|
80
100
|
serverError(message?: string): Response;
|
|
81
101
|
}
|
|
82
102
|
}
|
|
83
103
|
}
|
|
84
104
|
|
|
85
|
-
|
|
105
|
+
// Exported functions
|
|
106
|
+
|
|
107
|
+
/** Express middleware — attaches all response helpers to res */
|
|
108
|
+
export declare function responder(options?: ResponderOptions): RequestHandler;
|
|
109
|
+
|
|
110
|
+
/** Wraps an async route handler and forwards errors to next() automatically */
|
|
111
|
+
export declare function asyncHandler(
|
|
112
|
+
fn: (
|
|
113
|
+
req: Request,
|
|
114
|
+
res: Response,
|
|
115
|
+
next: NextFunction,
|
|
116
|
+
) => Promise<unknown> | unknown,
|
|
117
|
+
): RequestHandler;
|
|
86
118
|
|
|
87
119
|
export declare function successResponse(
|
|
88
120
|
res: Response,
|
|
89
|
-
data?:
|
|
121
|
+
data?: unknown,
|
|
90
122
|
message?: string,
|
|
91
123
|
statusCode?: number,
|
|
92
124
|
): Response;
|
|
@@ -95,14 +127,14 @@ export declare function errorResponse(
|
|
|
95
127
|
res: Response,
|
|
96
128
|
message?: string,
|
|
97
129
|
statusCode?: number,
|
|
98
|
-
errors?:
|
|
130
|
+
errors?: unknown,
|
|
99
131
|
): Response;
|
|
100
132
|
|
|
101
133
|
export declare function paginatedResponse(
|
|
102
134
|
res: Response,
|
|
103
|
-
data?:
|
|
135
|
+
data?: unknown[],
|
|
104
136
|
message?: string,
|
|
105
|
-
pagination?:
|
|
137
|
+
pagination?: PaginationInput,
|
|
106
138
|
): Response;
|
|
107
139
|
|
|
108
|
-
export default
|
|
140
|
+
export default responder;
|