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,138 +1,603 @@
1
1
  # Pagination
2
2
 
3
- Pagination helper provides a standard structure for paginated APIs.
3
+ This document covers pagination support in Response Kit v2.
4
4
 
5
5
  ---
6
6
 
7
- # Why Pagination?
7
+ ## Overview
8
8
 
9
- Imagine you have:
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
- ```text
12
- 10000 Users
13
- 50000 Products
14
- 100000 Orders
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
- Returning everything in one request is inefficient.
55
+ ### Syntax
18
56
 
19
- Pagination helps return data in chunks.
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
- # Syntax
107
+ ## Complete Examples
108
+
109
+ ### Example 1: Basic Pagination
24
110
 
25
111
  ```js
26
- response.paginate(
27
- res,
28
- data,
29
- page,
30
- limit,
31
- total
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
- # Example
165
+ ### Example 2: Pagination with Filtering
38
166
 
39
167
  ```js
40
- app.get("/users", (req, res) => {
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
- const users = [
43
- {
44
- id: 1,
45
- name: "John"
46
- },
47
- {
48
- id: 2,
49
- name: "Jane"
50
- }
51
- ];
52
-
53
- response.paginate(
54
- res,
55
- users,
56
- 1,
57
- 10,
58
- 100
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
- # Output
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
- "data": [
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
- # Explanation
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
- | Property | Description |
95
- | ---------- | --------------------- |
96
- | page | Current page |
97
- | limit | Items per page |
98
- | total | Total records |
99
- | totalPages | Total pages available |
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
- # MongoDB Example
522
+ ## Performance Optimization
523
+
524
+ ### 1. Use Database Indexes
104
525
 
105
526
  ```js
106
- const page = Number(req.query.page) || 1;
107
- const limit = Number(req.query.limit) || 10;
527
+ // MongoDB
528
+ await User.createIndex({ createdAt: -1 });
529
+ await User.createIndex({ name: 1 });
108
530
 
109
- const skip = (page - 1) * limit;
531
+ // Significantly improves pagination performance
532
+ ```
110
533
 
111
- const users = await User.find()
112
- .skip(skip)
113
- .limit(limit);
534
+ ### 2. Cache Total Counts
114
535
 
115
- const total = await User.countDocuments();
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
- response.paginate(
118
- res,
119
- users,
120
- page,
121
- limit,
122
- total
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
- # Best Practices
571
+ ## TypeScript
129
572
 
130
- ✅ Always return pagination object
573
+ ```typescript
574
+ import { ExtendedResponse } from 'response-kit';
575
+ import { Request } from 'express';
131
576
 
132
- Include total count
577
+ interface User {
578
+ id: number;
579
+ name: string;
580
+ email: string;
581
+ }
133
582
 
134
- Use page & limit query parameters
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
- Keep response structure consistent
599
+ ## Related
137
600
 
138
- This makes frontend pagination implementation much easier.
601
+ - [Success Responses](./success-response.md)
602
+ - [Error Responses](./error-response.md)
603
+ - [Getting Started](./getting-started.md)