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/pagination.md
CHANGED
|
@@ -1,138 +1,603 @@
|
|
|
1
1
|
# Pagination
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This document covers pagination support in Response Kit v2.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## Overview
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Response Kit provides a `paginate()` helper for sending paginated responses with metadata. It automatically calculates total pages and includes pagination information in the response.
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
**Available as:**
|
|
12
|
+
- **Middleware API** (v2): `res.paginate(data, page, limit, total)`
|
|
13
|
+
- **Direct API** (v1): `response.paginate(res, data, page, limit, total)`
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## paginate()
|
|
18
|
+
|
|
19
|
+
### Using Middleware (Recommended)
|
|
20
|
+
|
|
21
|
+
```js
|
|
22
|
+
app.use(response.middleware());
|
|
23
|
+
|
|
24
|
+
app.get('/users', response.asyncHandler(async (req, res) => {
|
|
25
|
+
const page = parseInt(req.query.page) || 1;
|
|
26
|
+
const limit = parseInt(req.query.limit) || 10;
|
|
27
|
+
|
|
28
|
+
const users = await User.find()
|
|
29
|
+
.skip((page - 1) * limit)
|
|
30
|
+
.limit(limit);
|
|
31
|
+
|
|
32
|
+
const total = await User.countDocuments();
|
|
33
|
+
|
|
34
|
+
res.paginate(users, page, limit, total);
|
|
35
|
+
}));
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Using Direct API
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
app.get('/users', async (req, res) => {
|
|
42
|
+
const page = parseInt(req.query.page) || 1;
|
|
43
|
+
const limit = parseInt(req.query.limit) || 10;
|
|
44
|
+
|
|
45
|
+
const users = await User.find()
|
|
46
|
+
.skip((page - 1) * limit)
|
|
47
|
+
.limit(limit);
|
|
48
|
+
|
|
49
|
+
const total = await User.countDocuments();
|
|
50
|
+
|
|
51
|
+
response.paginate(res, users, page, limit, total);
|
|
52
|
+
});
|
|
15
53
|
```
|
|
16
54
|
|
|
17
|
-
|
|
55
|
+
### Syntax
|
|
18
56
|
|
|
19
|
-
|
|
57
|
+
**Middleware API:**
|
|
58
|
+
```js
|
|
59
|
+
res.paginate(data, page, limit, total)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Direct API:**
|
|
63
|
+
```js
|
|
64
|
+
response.paginate(res, data, page, limit, total)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Parameters
|
|
68
|
+
|
|
69
|
+
| Parameter | Type | Required | Description |
|
|
70
|
+
|-----------|------|----------|-------------|
|
|
71
|
+
| `data` | array | Yes | Array of items for current page |
|
|
72
|
+
| `page` | number | Yes | Current page number (1-indexed) |
|
|
73
|
+
| `limit` | number | Yes | Number of items per page |
|
|
74
|
+
| `total` | number | Yes | Total number of items across all pages |
|
|
75
|
+
|
|
76
|
+
### Response Structure
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"success": true,
|
|
81
|
+
"data": [
|
|
82
|
+
{ "id": 1, "name": "John Doe" },
|
|
83
|
+
{ "id": 2, "name": "Jane Smith" }
|
|
84
|
+
],
|
|
85
|
+
"pagination": {
|
|
86
|
+
"page": 1,
|
|
87
|
+
"limit": 10,
|
|
88
|
+
"total": 100,
|
|
89
|
+
"totalPages": 10
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**Status Code:** `200 OK`
|
|
95
|
+
|
|
96
|
+
### Pagination Metadata
|
|
97
|
+
|
|
98
|
+
The `pagination` object includes:
|
|
99
|
+
|
|
100
|
+
- **`page`**: Current page number
|
|
101
|
+
- **`limit`**: Items per page
|
|
102
|
+
- **`total`**: Total number of items
|
|
103
|
+
- **`totalPages`**: Calculated as `Math.ceil(total / limit)`
|
|
20
104
|
|
|
21
105
|
---
|
|
22
106
|
|
|
23
|
-
|
|
107
|
+
## Complete Examples
|
|
108
|
+
|
|
109
|
+
### Example 1: Basic Pagination
|
|
24
110
|
|
|
25
111
|
```js
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
)
|
|
112
|
+
const express = require('express');
|
|
113
|
+
const response = require('response-kit');
|
|
114
|
+
|
|
115
|
+
const app = express();
|
|
116
|
+
app.use(response.middleware());
|
|
117
|
+
|
|
118
|
+
app.get('/users', response.asyncHandler(async (req, res) => {
|
|
119
|
+
// Parse query parameters
|
|
120
|
+
const page = parseInt(req.query.page) || 1;
|
|
121
|
+
const limit = parseInt(req.query.limit) || 10;
|
|
122
|
+
|
|
123
|
+
// Fetch paginated data
|
|
124
|
+
const users = await User.find()
|
|
125
|
+
.skip((page - 1) * limit)
|
|
126
|
+
.limit(limit);
|
|
127
|
+
|
|
128
|
+
// Get total count
|
|
129
|
+
const total = await User.countDocuments();
|
|
130
|
+
|
|
131
|
+
// Send paginated response
|
|
132
|
+
res.paginate(users, page, limit, total);
|
|
133
|
+
}));
|
|
134
|
+
|
|
135
|
+
app.listen(3000);
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**Request:**
|
|
139
|
+
```
|
|
140
|
+
GET /users?page=2&limit=5
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**Response:**
|
|
144
|
+
```json
|
|
145
|
+
{
|
|
146
|
+
"success": true,
|
|
147
|
+
"data": [
|
|
148
|
+
{ "id": 6, "name": "User 6" },
|
|
149
|
+
{ "id": 7, "name": "User 7" },
|
|
150
|
+
{ "id": 8, "name": "User 8" },
|
|
151
|
+
{ "id": 9, "name": "User 9" },
|
|
152
|
+
{ "id": 10, "name": "User 10" }
|
|
153
|
+
],
|
|
154
|
+
"pagination": {
|
|
155
|
+
"page": 2,
|
|
156
|
+
"limit": 5,
|
|
157
|
+
"total": 50,
|
|
158
|
+
"totalPages": 10
|
|
159
|
+
}
|
|
160
|
+
}
|
|
33
161
|
```
|
|
34
162
|
|
|
35
163
|
---
|
|
36
164
|
|
|
37
|
-
|
|
165
|
+
### Example 2: Pagination with Filtering
|
|
38
166
|
|
|
39
167
|
```js
|
|
40
|
-
app.get(
|
|
168
|
+
app.get('/users', response.asyncHandler(async (req, res) => {
|
|
169
|
+
const page = parseInt(req.query.page) || 1;
|
|
170
|
+
const limit = parseInt(req.query.limit) || 10;
|
|
171
|
+
const search = req.query.search || '';
|
|
172
|
+
|
|
173
|
+
// Build query
|
|
174
|
+
const query = search
|
|
175
|
+
? { name: { $regex: search, $options: 'i' } }
|
|
176
|
+
: {};
|
|
177
|
+
|
|
178
|
+
// Fetch filtered and paginated data
|
|
179
|
+
const users = await User.find(query)
|
|
180
|
+
.skip((page - 1) * limit)
|
|
181
|
+
.limit(limit);
|
|
182
|
+
|
|
183
|
+
// Count filtered results
|
|
184
|
+
const total = await User.countDocuments(query);
|
|
185
|
+
|
|
186
|
+
res.paginate(users, page, limit, total);
|
|
187
|
+
}));
|
|
188
|
+
```
|
|
41
189
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
190
|
+
**Request:**
|
|
191
|
+
```
|
|
192
|
+
GET /users?page=1&limit=10&search=john
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
### Example 3: Pagination with Sorting
|
|
198
|
+
|
|
199
|
+
```js
|
|
200
|
+
app.get('/posts', response.asyncHandler(async (req, res) => {
|
|
201
|
+
const page = parseInt(req.query.page) || 1;
|
|
202
|
+
const limit = parseInt(req.query.limit) || 10;
|
|
203
|
+
const sortBy = req.query.sortBy || 'createdAt';
|
|
204
|
+
const sortOrder = req.query.sortOrder === 'asc' ? 1 : -1;
|
|
205
|
+
|
|
206
|
+
const posts = await Post.find()
|
|
207
|
+
.sort({ [sortBy]: sortOrder })
|
|
208
|
+
.skip((page - 1) * limit)
|
|
209
|
+
.limit(limit)
|
|
210
|
+
.populate('author', 'name email');
|
|
211
|
+
|
|
212
|
+
const total = await Post.countDocuments();
|
|
213
|
+
|
|
214
|
+
res.paginate(posts, page, limit, total);
|
|
215
|
+
}));
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
**Request:**
|
|
219
|
+
```
|
|
220
|
+
GET /posts?page=1&limit=20&sortBy=title&sortOrder=asc
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
### Example 4: Advanced Pagination Helper
|
|
226
|
+
|
|
227
|
+
Create a reusable pagination helper:
|
|
228
|
+
|
|
229
|
+
```js
|
|
230
|
+
// helpers/pagination.js
|
|
231
|
+
async function paginateModel(Model, page, limit, filter = {}, sort = {}) {
|
|
232
|
+
const skip = (page - 1) * limit;
|
|
233
|
+
|
|
234
|
+
const [data, total] = await Promise.all([
|
|
235
|
+
Model.find(filter)
|
|
236
|
+
.sort(sort)
|
|
237
|
+
.skip(skip)
|
|
238
|
+
.limit(limit),
|
|
239
|
+
Model.countDocuments(filter)
|
|
240
|
+
]);
|
|
241
|
+
|
|
242
|
+
return { data, total };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
module.exports = { paginateModel };
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
**Usage:**
|
|
249
|
+
|
|
250
|
+
```js
|
|
251
|
+
const { paginateModel } = require('./helpers/pagination');
|
|
252
|
+
|
|
253
|
+
app.get('/users', response.asyncHandler(async (req, res) => {
|
|
254
|
+
const page = parseInt(req.query.page) || 1;
|
|
255
|
+
const limit = parseInt(req.query.limit) || 10;
|
|
256
|
+
|
|
257
|
+
const { data, total } = await paginateModel(
|
|
258
|
+
User,
|
|
259
|
+
page,
|
|
260
|
+
limit,
|
|
261
|
+
{ active: true },
|
|
262
|
+
{ createdAt: -1 }
|
|
59
263
|
);
|
|
264
|
+
|
|
265
|
+
res.paginate(data, page, limit, total);
|
|
266
|
+
}));
|
|
267
|
+
```
|
|
60
268
|
|
|
61
|
-
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
### Example 5: Pagination with Validation
|
|
272
|
+
|
|
273
|
+
```js
|
|
274
|
+
app.get('/products', response.asyncHandler(async (req, res) => {
|
|
275
|
+
const page = parseInt(req.query.page) || 1;
|
|
276
|
+
const limit = parseInt(req.query.limit) || 10;
|
|
277
|
+
|
|
278
|
+
// Validation
|
|
279
|
+
if (page < 1) {
|
|
280
|
+
return res.badRequest('Page must be greater than 0');
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (limit < 1 || limit > 100) {
|
|
284
|
+
return res.badRequest('Limit must be between 1 and 100');
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const products = await Product.find()
|
|
288
|
+
.skip((page - 1) * limit)
|
|
289
|
+
.limit(limit);
|
|
290
|
+
|
|
291
|
+
const total = await Product.countDocuments();
|
|
292
|
+
|
|
293
|
+
res.paginate(products, page, limit, total);
|
|
294
|
+
}));
|
|
62
295
|
```
|
|
63
296
|
|
|
64
297
|
---
|
|
65
298
|
|
|
66
|
-
|
|
299
|
+
### Example 6: Cursor-Based Pagination Alternative
|
|
67
300
|
|
|
301
|
+
For cursor-based pagination (useful for large datasets):
|
|
302
|
+
|
|
303
|
+
```js
|
|
304
|
+
app.get('/posts/cursor', response.asyncHandler(async (req, res) => {
|
|
305
|
+
const limit = parseInt(req.query.limit) || 10;
|
|
306
|
+
const cursor = req.query.cursor; // Last seen ID
|
|
307
|
+
|
|
308
|
+
const query = cursor ? { _id: { $gt: cursor } } : {};
|
|
309
|
+
|
|
310
|
+
const posts = await Post.find(query)
|
|
311
|
+
.sort({ _id: 1 })
|
|
312
|
+
.limit(limit + 1); // Fetch one extra to check if there's a next page
|
|
313
|
+
|
|
314
|
+
const hasMore = posts.length > limit;
|
|
315
|
+
const data = hasMore ? posts.slice(0, limit) : posts;
|
|
316
|
+
const nextCursor = hasMore ? data[data.length - 1]._id : null;
|
|
317
|
+
|
|
318
|
+
res.success({
|
|
319
|
+
data,
|
|
320
|
+
cursor: {
|
|
321
|
+
next: nextCursor,
|
|
322
|
+
hasMore
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
}));
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
## Pagination Patterns
|
|
331
|
+
|
|
332
|
+
### Pattern 1: Offset-Based (Default)
|
|
333
|
+
|
|
334
|
+
```js
|
|
335
|
+
// Using skip and limit
|
|
336
|
+
const skip = (page - 1) * limit;
|
|
337
|
+
const data = await Model.find().skip(skip).limit(limit);
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
**Pros:**
|
|
341
|
+
- Simple to implement
|
|
342
|
+
- Direct page access
|
|
343
|
+
- Familiar to users
|
|
344
|
+
|
|
345
|
+
**Cons:**
|
|
346
|
+
- Performance degrades with large offsets
|
|
347
|
+
- Data consistency issues with concurrent updates
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
### Pattern 2: Cursor-Based
|
|
352
|
+
|
|
353
|
+
```js
|
|
354
|
+
// Using a cursor (last seen ID)
|
|
355
|
+
const data = await Model.find({ _id: { $gt: cursor } })
|
|
356
|
+
.limit(limit);
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
**Pros:**
|
|
360
|
+
- Better performance for large datasets
|
|
361
|
+
- Consistent results
|
|
362
|
+
- No skipped/duplicate items
|
|
363
|
+
|
|
364
|
+
**Cons:**
|
|
365
|
+
- Can't jump to arbitrary pages
|
|
366
|
+
- Requires indexed cursor field
|
|
367
|
+
|
|
368
|
+
---
|
|
369
|
+
|
|
370
|
+
## Best Practices
|
|
371
|
+
|
|
372
|
+
### ✅ Do's
|
|
373
|
+
|
|
374
|
+
- Validate page and limit parameters
|
|
375
|
+
- Set reasonable default and maximum limits
|
|
376
|
+
- Use efficient database queries (indexes)
|
|
377
|
+
- Count totals efficiently (consider caching for large datasets)
|
|
378
|
+
- Return empty array for out-of-range pages
|
|
379
|
+
|
|
380
|
+
```js
|
|
381
|
+
// ✅ Good
|
|
382
|
+
const page = Math.max(1, parseInt(req.query.page) || 1);
|
|
383
|
+
const limit = Math.min(100, Math.max(1, parseInt(req.query.limit) || 10));
|
|
384
|
+
|
|
385
|
+
// Use indexes
|
|
386
|
+
await User.createIndex({ createdAt: -1 });
|
|
387
|
+
|
|
388
|
+
// Empty result for out-of-range page
|
|
389
|
+
if (page > totalPages) {
|
|
390
|
+
return res.paginate([], page, limit, total);
|
|
391
|
+
}
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
### ❌ Don'ts
|
|
395
|
+
|
|
396
|
+
- Don't allow unlimited page sizes
|
|
397
|
+
- Don't skip validation
|
|
398
|
+
- Don't use pagination for very large datasets without optimization
|
|
399
|
+
- Don't fetch all data and paginate in memory
|
|
400
|
+
|
|
401
|
+
```js
|
|
402
|
+
// ❌ Bad
|
|
403
|
+
const limit = parseInt(req.query.limit); // No validation!
|
|
404
|
+
|
|
405
|
+
// ❌ Bad - fetches everything
|
|
406
|
+
const allUsers = await User.find();
|
|
407
|
+
const paginated = allUsers.slice((page - 1) * limit, page * limit);
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
412
|
+
## With Configuration
|
|
413
|
+
|
|
414
|
+
Pagination respects global configuration:
|
|
415
|
+
|
|
416
|
+
```js
|
|
417
|
+
response.configure({
|
|
418
|
+
dataKey: 'items',
|
|
419
|
+
includeTimestamp: true,
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
res.paginate(users, 1, 10, 100);
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
**Response:**
|
|
68
426
|
```json
|
|
69
427
|
{
|
|
70
428
|
"success": true,
|
|
71
|
-
"
|
|
72
|
-
{
|
|
73
|
-
"id": 1,
|
|
74
|
-
"name": "John"
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
"id": 2,
|
|
78
|
-
"name": "Jane"
|
|
79
|
-
}
|
|
80
|
-
],
|
|
429
|
+
"items": [...],
|
|
81
430
|
"pagination": {
|
|
82
431
|
"page": 1,
|
|
83
432
|
"limit": 10,
|
|
84
433
|
"total": 100,
|
|
85
434
|
"totalPages": 10
|
|
86
|
-
}
|
|
435
|
+
},
|
|
436
|
+
"timestamp": "2024-01-15T10:30:00.000Z"
|
|
87
437
|
}
|
|
88
438
|
```
|
|
89
439
|
|
|
90
440
|
---
|
|
91
441
|
|
|
92
|
-
|
|
442
|
+
## Client-Side Usage
|
|
443
|
+
|
|
444
|
+
### Using Axios
|
|
445
|
+
|
|
446
|
+
```js
|
|
447
|
+
async function fetchUsers(page = 1, limit = 10) {
|
|
448
|
+
const response = await axios.get('/users', {
|
|
449
|
+
params: { page, limit }
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
return response.data;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Usage
|
|
456
|
+
const result = await fetchUsers(2, 20);
|
|
457
|
+
console.log(result.data); // Users array
|
|
458
|
+
console.log(result.pagination.totalPages); // Total pages
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
### Using Fetch API
|
|
462
|
+
|
|
463
|
+
```js
|
|
464
|
+
async function fetchUsers(page = 1, limit = 10) {
|
|
465
|
+
const url = `/users?page=${page}&limit=${limit}`;
|
|
466
|
+
const response = await fetch(url);
|
|
467
|
+
return response.json();
|
|
468
|
+
}
|
|
469
|
+
```
|
|
93
470
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
471
|
+
### React Example
|
|
472
|
+
|
|
473
|
+
```jsx
|
|
474
|
+
import { useState, useEffect } from 'react';
|
|
475
|
+
|
|
476
|
+
function UserList() {
|
|
477
|
+
const [users, setUsers] = useState([]);
|
|
478
|
+
const [pagination, setPagination] = useState({});
|
|
479
|
+
const [page, setPage] = useState(1);
|
|
480
|
+
|
|
481
|
+
useEffect(() => {
|
|
482
|
+
fetchUsers(page).then(result => {
|
|
483
|
+
setUsers(result.data);
|
|
484
|
+
setPagination(result.pagination);
|
|
485
|
+
});
|
|
486
|
+
}, [page]);
|
|
487
|
+
|
|
488
|
+
return (
|
|
489
|
+
<div>
|
|
490
|
+
<ul>
|
|
491
|
+
{users.map(user => (
|
|
492
|
+
<li key={user.id}>{user.name}</li>
|
|
493
|
+
))}
|
|
494
|
+
</ul>
|
|
495
|
+
|
|
496
|
+
<div>
|
|
497
|
+
<button
|
|
498
|
+
disabled={page === 1}
|
|
499
|
+
onClick={() => setPage(page - 1)}
|
|
500
|
+
>
|
|
501
|
+
Previous
|
|
502
|
+
</button>
|
|
503
|
+
|
|
504
|
+
<span>
|
|
505
|
+
Page {pagination.page} of {pagination.totalPages}
|
|
506
|
+
</span>
|
|
507
|
+
|
|
508
|
+
<button
|
|
509
|
+
disabled={page === pagination.totalPages}
|
|
510
|
+
onClick={() => setPage(page + 1)}
|
|
511
|
+
>
|
|
512
|
+
Next
|
|
513
|
+
</button>
|
|
514
|
+
</div>
|
|
515
|
+
</div>
|
|
516
|
+
);
|
|
517
|
+
}
|
|
518
|
+
```
|
|
100
519
|
|
|
101
520
|
---
|
|
102
521
|
|
|
103
|
-
|
|
522
|
+
## Performance Optimization
|
|
523
|
+
|
|
524
|
+
### 1. Use Database Indexes
|
|
104
525
|
|
|
105
526
|
```js
|
|
106
|
-
|
|
107
|
-
|
|
527
|
+
// MongoDB
|
|
528
|
+
await User.createIndex({ createdAt: -1 });
|
|
529
|
+
await User.createIndex({ name: 1 });
|
|
108
530
|
|
|
109
|
-
|
|
531
|
+
// Significantly improves pagination performance
|
|
532
|
+
```
|
|
110
533
|
|
|
111
|
-
|
|
112
|
-
.skip(skip)
|
|
113
|
-
.limit(limit);
|
|
534
|
+
### 2. Cache Total Counts
|
|
114
535
|
|
|
115
|
-
|
|
536
|
+
```js
|
|
537
|
+
const cache = new Map();
|
|
538
|
+
|
|
539
|
+
app.get('/users', response.asyncHandler(async (req, res) => {
|
|
540
|
+
const page = parseInt(req.query.page) || 1;
|
|
541
|
+
const limit = parseInt(req.query.limit) || 10;
|
|
542
|
+
|
|
543
|
+
const users = await User.find()
|
|
544
|
+
.skip((page - 1) * limit)
|
|
545
|
+
.limit(limit);
|
|
546
|
+
|
|
547
|
+
// Cache total count for 5 minutes
|
|
548
|
+
let total = cache.get('user_count');
|
|
549
|
+
if (!total) {
|
|
550
|
+
total = await User.countDocuments();
|
|
551
|
+
cache.set('user_count', total);
|
|
552
|
+
setTimeout(() => cache.delete('user_count'), 5 * 60 * 1000);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
res.paginate(users, page, limit, total);
|
|
556
|
+
}));
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
### 3. Use Lean Queries
|
|
116
560
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
limit
|
|
122
|
-
|
|
123
|
-
);
|
|
561
|
+
```js
|
|
562
|
+
// Mongoose - return plain objects instead of documents
|
|
563
|
+
const users = await User.find()
|
|
564
|
+
.lean() // Faster, less memory
|
|
565
|
+
.skip((page - 1) * limit)
|
|
566
|
+
.limit(limit);
|
|
124
567
|
```
|
|
125
568
|
|
|
126
569
|
---
|
|
127
570
|
|
|
128
|
-
|
|
571
|
+
## TypeScript
|
|
129
572
|
|
|
130
|
-
|
|
573
|
+
```typescript
|
|
574
|
+
import { ExtendedResponse } from 'response-kit';
|
|
575
|
+
import { Request } from 'express';
|
|
131
576
|
|
|
132
|
-
|
|
577
|
+
interface User {
|
|
578
|
+
id: number;
|
|
579
|
+
name: string;
|
|
580
|
+
email: string;
|
|
581
|
+
}
|
|
133
582
|
|
|
134
|
-
|
|
583
|
+
app.get('/users', async (req: Request, res: ExtendedResponse) => {
|
|
584
|
+
const page = parseInt(req.query.page as string) || 1;
|
|
585
|
+
const limit = parseInt(req.query.limit as string) || 10;
|
|
586
|
+
|
|
587
|
+
const users: User[] = await User.find()
|
|
588
|
+
.skip((page - 1) * limit)
|
|
589
|
+
.limit(limit);
|
|
590
|
+
|
|
591
|
+
const total = await User.countDocuments();
|
|
592
|
+
|
|
593
|
+
res.paginate(users, page, limit, total);
|
|
594
|
+
});
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
---
|
|
135
598
|
|
|
136
|
-
|
|
599
|
+
## Related
|
|
137
600
|
|
|
138
|
-
|
|
601
|
+
- [Success Responses](./success-response.md)
|
|
602
|
+
- [Error Responses](./error-response.md)
|
|
603
|
+
- [Getting Started](./getting-started.md)
|