@aicgen/aicgen 1.0.1 → 1.0.2
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/AGENTS.md +7 -2
- package/claude.md +1 -1
- package/dist/index.js +6272 -5503
- package/package.json +1 -1
- package/.agent/rules/api-design.md +0 -649
- package/.agent/rules/architecture.md +0 -2507
- package/.agent/rules/best-practices.md +0 -622
- package/.agent/rules/code-style.md +0 -308
- package/.agent/rules/design-patterns.md +0 -577
- package/.agent/rules/devops.md +0 -230
- package/.agent/rules/error-handling.md +0 -417
- package/.agent/rules/instructions.md +0 -28
- package/.agent/rules/language.md +0 -786
- package/.agent/rules/performance.md +0 -710
- package/.agent/rules/security.md +0 -587
- package/.agent/rules/testing.md +0 -572
- package/.agent/workflows/add-documentation.md +0 -10
- package/.agent/workflows/generate-integration-tests.md +0 -10
- package/.agent/workflows/generate-unit-tests.md +0 -11
- package/.agent/workflows/performance-audit.md +0 -11
- package/.agent/workflows/refactor-extract-module.md +0 -12
- package/.agent/workflows/security-audit.md +0 -12
- package/.gemini/instructions.md +0 -4843
package/package.json
CHANGED
|
@@ -1,649 +0,0 @@
|
|
|
1
|
-
# API Design Rules
|
|
2
|
-
|
|
3
|
-
# API Basics
|
|
4
|
-
|
|
5
|
-
## HTTP Methods
|
|
6
|
-
|
|
7
|
-
Use the right method for each operation:
|
|
8
|
-
|
|
9
|
-
| Method | Purpose | Example |
|
|
10
|
-
|--------|---------|---------|
|
|
11
|
-
| GET | Read data | Get list of users |
|
|
12
|
-
| POST | Create new resource | Create a new user |
|
|
13
|
-
| PUT | Replace entire resource | Update all user fields |
|
|
14
|
-
| PATCH | Update part of resource | Update user's email only |
|
|
15
|
-
| DELETE | Remove resource | Delete a user |
|
|
16
|
-
|
|
17
|
-
## GET - Reading Data
|
|
18
|
-
|
|
19
|
-
```pseudocode
|
|
20
|
-
// Get all items
|
|
21
|
-
route GET "/api/users":
|
|
22
|
-
users = getAllUsers()
|
|
23
|
-
return JSON(users)
|
|
24
|
-
|
|
25
|
-
// Get single item by ID
|
|
26
|
-
route GET "/api/users/:id":
|
|
27
|
-
user = getUserById(params.id)
|
|
28
|
-
if user is null:
|
|
29
|
-
return status 404, JSON({ error: "User not found" })
|
|
30
|
-
return JSON(user)
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
## POST - Creating Data
|
|
34
|
-
|
|
35
|
-
```pseudocode
|
|
36
|
-
route POST "/api/users":
|
|
37
|
-
name = request.body.name
|
|
38
|
-
email = request.body.email
|
|
39
|
-
|
|
40
|
-
// Validate input
|
|
41
|
-
if name is empty or email is empty:
|
|
42
|
-
return status 400, JSON({ error: "Name and email required" })
|
|
43
|
-
|
|
44
|
-
newUser = createUser({ name, email })
|
|
45
|
-
|
|
46
|
-
// Return 201 Created with new resource
|
|
47
|
-
return status 201, JSON(newUser)
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
## PUT - Replacing Data
|
|
51
|
-
|
|
52
|
-
```pseudocode
|
|
53
|
-
route PUT "/api/users/:id":
|
|
54
|
-
id = params.id
|
|
55
|
-
name = request.body.name
|
|
56
|
-
email = request.body.email
|
|
57
|
-
|
|
58
|
-
user = getUserById(id)
|
|
59
|
-
if user is null:
|
|
60
|
-
return status 404, JSON({ error: "User not found" })
|
|
61
|
-
|
|
62
|
-
updated = replaceUser(id, { name, email })
|
|
63
|
-
return JSON(updated)
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
## PATCH - Updating Data
|
|
67
|
-
|
|
68
|
-
```pseudocode
|
|
69
|
-
route PATCH "/api/users/:id":
|
|
70
|
-
id = params.id
|
|
71
|
-
updates = request.body // Only fields to update
|
|
72
|
-
|
|
73
|
-
user = getUserById(id)
|
|
74
|
-
if user is null:
|
|
75
|
-
return status 404, JSON({ error: "User not found" })
|
|
76
|
-
|
|
77
|
-
updated = updateUser(id, updates)
|
|
78
|
-
return JSON(updated)
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
## DELETE - Removing Data
|
|
82
|
-
|
|
83
|
-
```pseudocode
|
|
84
|
-
route DELETE "/api/users/:id":
|
|
85
|
-
id = params.id
|
|
86
|
-
|
|
87
|
-
user = getUserById(id)
|
|
88
|
-
if user is null:
|
|
89
|
-
return status 404, JSON({ error: "User not found" })
|
|
90
|
-
|
|
91
|
-
deleteUser(id)
|
|
92
|
-
|
|
93
|
-
// 204 No Content - successful deletion
|
|
94
|
-
return status 204
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
## HTTP Status Codes
|
|
98
|
-
|
|
99
|
-
### Success Codes (2xx)
|
|
100
|
-
|
|
101
|
-
```pseudocode
|
|
102
|
-
// 200 OK - Request succeeded
|
|
103
|
-
return status 200, JSON(data)
|
|
104
|
-
|
|
105
|
-
// 201 Created - New resource created
|
|
106
|
-
return status 201, JSON(newResource)
|
|
107
|
-
|
|
108
|
-
// 204 No Content - Success with no response body
|
|
109
|
-
return status 204
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
### Client Error Codes (4xx)
|
|
113
|
-
|
|
114
|
-
```pseudocode
|
|
115
|
-
// 400 Bad Request - Invalid input
|
|
116
|
-
return status 400, JSON({ error: "Invalid email format" })
|
|
117
|
-
|
|
118
|
-
// 401 Unauthorized - Not authenticated
|
|
119
|
-
return status 401, JSON({ error: "Login required" })
|
|
120
|
-
|
|
121
|
-
// 403 Forbidden - Authenticated but not allowed
|
|
122
|
-
return status 403, JSON({ error: "Admin access required" })
|
|
123
|
-
|
|
124
|
-
// 404 Not Found - Resource doesn't exist
|
|
125
|
-
return status 404, JSON({ error: "User not found" })
|
|
126
|
-
|
|
127
|
-
// 409 Conflict - Resource already exists
|
|
128
|
-
return status 409, JSON({ error: "Email already registered" })
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
### Server Error Codes (5xx)
|
|
132
|
-
|
|
133
|
-
```pseudocode
|
|
134
|
-
// 500 Internal Server Error - Unexpected error
|
|
135
|
-
return status 500, JSON({ error: "Internal server error" })
|
|
136
|
-
|
|
137
|
-
// 503 Service Unavailable - Temporary issue
|
|
138
|
-
return status 503, JSON({ error: "Database unavailable" })
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
## URL Structure
|
|
142
|
-
|
|
143
|
-
Use clear, hierarchical URLs:
|
|
144
|
-
|
|
145
|
-
```
|
|
146
|
-
✅ Good
|
|
147
|
-
GET /api/users # List all users
|
|
148
|
-
GET /api/users/123 # Get user 123
|
|
149
|
-
POST /api/users # Create user
|
|
150
|
-
GET /api/users/123/posts # Get posts by user 123
|
|
151
|
-
|
|
152
|
-
❌ Bad
|
|
153
|
-
GET /api/getUsers
|
|
154
|
-
POST /api/createUser
|
|
155
|
-
GET /api/user?id=123
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
## Request and Response Format
|
|
159
|
-
|
|
160
|
-
### JSON Request Body
|
|
161
|
-
|
|
162
|
-
```
|
|
163
|
-
// Client sends
|
|
164
|
-
POST /api/users
|
|
165
|
-
Content-Type: application/json
|
|
166
|
-
|
|
167
|
-
{
|
|
168
|
-
"name": "Alice",
|
|
169
|
-
"email": "alice@example.com"
|
|
170
|
-
}
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
### JSON Response
|
|
174
|
-
|
|
175
|
-
```
|
|
176
|
-
// Server responds
|
|
177
|
-
HTTP/1.1 201 Created
|
|
178
|
-
Content-Type: application/json
|
|
179
|
-
|
|
180
|
-
{
|
|
181
|
-
"id": 123,
|
|
182
|
-
"name": "Alice",
|
|
183
|
-
"email": "alice@example.com",
|
|
184
|
-
"createdAt": "2024-01-15T10:30:00Z"
|
|
185
|
-
}
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
## Query Parameters
|
|
189
|
-
|
|
190
|
-
Use query parameters for filtering, sorting, and pagination:
|
|
191
|
-
|
|
192
|
-
```pseudocode
|
|
193
|
-
// Filter by status
|
|
194
|
-
// GET /api/orders?status=pending
|
|
195
|
-
route GET "/api/orders":
|
|
196
|
-
status = query.status
|
|
197
|
-
orders = getOrders({ status })
|
|
198
|
-
return JSON(orders)
|
|
199
|
-
|
|
200
|
-
// Sort by field
|
|
201
|
-
// GET /api/users?sort=name
|
|
202
|
-
|
|
203
|
-
// Pagination
|
|
204
|
-
// GET /api/users?page=2&limit=20
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
## Error Responses
|
|
208
|
-
|
|
209
|
-
Always return consistent error format:
|
|
210
|
-
|
|
211
|
-
```pseudocode
|
|
212
|
-
// ✅ Good: Structured error
|
|
213
|
-
return status 400, JSON({
|
|
214
|
-
error: {
|
|
215
|
-
code: "VALIDATION_ERROR",
|
|
216
|
-
message: "Invalid input",
|
|
217
|
-
details: {
|
|
218
|
-
email: "Email format is invalid"
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
})
|
|
222
|
-
|
|
223
|
-
// ❌ Bad: Inconsistent
|
|
224
|
-
return status 400, "Bad request"
|
|
225
|
-
return status 400, JSON({ msg: "Error" })
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
## Best Practices
|
|
229
|
-
|
|
230
|
-
1. **Use correct HTTP methods** - GET for reading, POST for creating, etc.
|
|
231
|
-
2. **Use appropriate status codes** - 200 for success, 404 for not found, etc.
|
|
232
|
-
3. **Return JSON** - Standard format for APIs
|
|
233
|
-
4. **Validate input** - Check data before processing
|
|
234
|
-
5. **Handle errors** - Return clear error messages
|
|
235
|
-
|
|
236
|
-
```pseudocode
|
|
237
|
-
// Complete example
|
|
238
|
-
route POST "/api/products":
|
|
239
|
-
name = request.body.name
|
|
240
|
-
price = request.body.price
|
|
241
|
-
|
|
242
|
-
// Validate
|
|
243
|
-
if name is empty or price is empty:
|
|
244
|
-
return status 400, JSON({
|
|
245
|
-
error: "Name and price are required"
|
|
246
|
-
})
|
|
247
|
-
|
|
248
|
-
if price < 0:
|
|
249
|
-
return status 400, JSON({
|
|
250
|
-
error: "Price cannot be negative"
|
|
251
|
-
})
|
|
252
|
-
|
|
253
|
-
// Check for duplicates
|
|
254
|
-
if productExists(name):
|
|
255
|
-
return status 409, JSON({
|
|
256
|
-
error: "Product already exists"
|
|
257
|
-
})
|
|
258
|
-
|
|
259
|
-
// Create
|
|
260
|
-
product = createProduct({ name, price })
|
|
261
|
-
|
|
262
|
-
// Return success
|
|
263
|
-
return status 201, JSON(product)
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
## Common Mistakes
|
|
267
|
-
|
|
268
|
-
```pseudocode
|
|
269
|
-
// ❌ Wrong method for operation
|
|
270
|
-
route GET "/api/users/delete/:id" // Should be DELETE
|
|
271
|
-
|
|
272
|
-
// ❌ Wrong status code
|
|
273
|
-
route POST "/api/users":
|
|
274
|
-
user = createUser(body)
|
|
275
|
-
return status 200, JSON(user) // Should be 201
|
|
276
|
-
|
|
277
|
-
// ❌ Not handling missing resources
|
|
278
|
-
route GET "/api/users/:id":
|
|
279
|
-
user = getUserById(params.id)
|
|
280
|
-
return JSON(user) // What if user is null?
|
|
281
|
-
|
|
282
|
-
// ✅ Correct
|
|
283
|
-
route DELETE "/api/users/:id"
|
|
284
|
-
|
|
285
|
-
route POST "/api/users":
|
|
286
|
-
user = createUser(body)
|
|
287
|
-
return status 201, JSON(user)
|
|
288
|
-
|
|
289
|
-
route GET "/api/users/:id":
|
|
290
|
-
user = getUserById(params.id)
|
|
291
|
-
if user is null:
|
|
292
|
-
return status 404, JSON({ error: "User not found" })
|
|
293
|
-
return JSON(user)
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
---
|
|
298
|
-
|
|
299
|
-
# REST API Design
|
|
300
|
-
|
|
301
|
-
## Resource-Oriented URLs
|
|
302
|
-
|
|
303
|
-
```
|
|
304
|
-
✅ Good (nouns, plural)
|
|
305
|
-
GET /api/v1/books # List books
|
|
306
|
-
GET /api/v1/books/123 # Get book
|
|
307
|
-
POST /api/v1/books # Create book
|
|
308
|
-
PUT /api/v1/books/123 # Replace book
|
|
309
|
-
PATCH /api/v1/books/123 # Update book
|
|
310
|
-
DELETE /api/v1/books/123 # Delete book
|
|
311
|
-
|
|
312
|
-
❌ Bad (verbs, actions)
|
|
313
|
-
POST /api/v1/createBook
|
|
314
|
-
GET /api/v1/getBookById/123
|
|
315
|
-
POST /api/v1/updateBook/123
|
|
316
|
-
```
|
|
317
|
-
|
|
318
|
-
## HTTP Methods
|
|
319
|
-
|
|
320
|
-
```typescript
|
|
321
|
-
// GET - Read (safe, idempotent)
|
|
322
|
-
app.get('/api/v1/users/:id', async (req, res) => {
|
|
323
|
-
const user = await userService.findById(req.params.id);
|
|
324
|
-
res.json({ data: user });
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
// POST - Create (not idempotent)
|
|
328
|
-
app.post('/api/v1/users', async (req, res) => {
|
|
329
|
-
const user = await userService.create(req.body);
|
|
330
|
-
res.status(201)
|
|
331
|
-
.location(`/api/v1/users/${user.id}`)
|
|
332
|
-
.json({ data: user });
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
// PUT - Replace entire resource (idempotent)
|
|
336
|
-
app.put('/api/v1/users/:id', async (req, res) => {
|
|
337
|
-
const user = await userService.replace(req.params.id, req.body);
|
|
338
|
-
res.json({ data: user });
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
// PATCH - Partial update (idempotent)
|
|
342
|
-
app.patch('/api/v1/users/:id', async (req, res) => {
|
|
343
|
-
const user = await userService.update(req.params.id, req.body);
|
|
344
|
-
res.json({ data: user });
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
// DELETE - Remove (idempotent)
|
|
348
|
-
app.delete('/api/v1/users/:id', async (req, res) => {
|
|
349
|
-
await userService.delete(req.params.id);
|
|
350
|
-
res.status(204).end();
|
|
351
|
-
});
|
|
352
|
-
```
|
|
353
|
-
|
|
354
|
-
## Status Codes
|
|
355
|
-
|
|
356
|
-
```typescript
|
|
357
|
-
// Success
|
|
358
|
-
200 OK // GET, PUT, PATCH succeeded
|
|
359
|
-
201 Created // POST succeeded
|
|
360
|
-
204 No Content // DELETE succeeded
|
|
361
|
-
|
|
362
|
-
// Client errors
|
|
363
|
-
400 Bad Request // Validation failed
|
|
364
|
-
401 Unauthorized // Not authenticated
|
|
365
|
-
403 Forbidden // Authenticated but not allowed
|
|
366
|
-
404 Not Found // Resource doesn't exist
|
|
367
|
-
409 Conflict // Duplicate, version conflict
|
|
368
|
-
422 Unprocessable // Business rule violation
|
|
369
|
-
|
|
370
|
-
// Server errors
|
|
371
|
-
500 Internal Server Error
|
|
372
|
-
```
|
|
373
|
-
|
|
374
|
-
## Response Format
|
|
375
|
-
|
|
376
|
-
```typescript
|
|
377
|
-
// Single resource
|
|
378
|
-
{
|
|
379
|
-
"data": {
|
|
380
|
-
"id": 123,
|
|
381
|
-
"name": "John Doe",
|
|
382
|
-
"email": "john@example.com"
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
// Collection with pagination
|
|
387
|
-
{
|
|
388
|
-
"data": [
|
|
389
|
-
{ "id": 1, "name": "Item 1" },
|
|
390
|
-
{ "id": 2, "name": "Item 2" }
|
|
391
|
-
],
|
|
392
|
-
"pagination": {
|
|
393
|
-
"page": 1,
|
|
394
|
-
"limit": 20,
|
|
395
|
-
"total": 150,
|
|
396
|
-
"totalPages": 8
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
// Error response
|
|
401
|
-
{
|
|
402
|
-
"error": {
|
|
403
|
-
"code": "VALIDATION_ERROR",
|
|
404
|
-
"message": "The request contains invalid data",
|
|
405
|
-
"details": [
|
|
406
|
-
{ "field": "email", "message": "Invalid email format" }
|
|
407
|
-
]
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
```
|
|
411
|
-
|
|
412
|
-
## Hierarchical Resources
|
|
413
|
-
|
|
414
|
-
```
|
|
415
|
-
✅ Limit nesting to 2-3 levels
|
|
416
|
-
GET /api/v1/authors/456/books # Books by author
|
|
417
|
-
GET /api/v1/orders/789/items # Items in order
|
|
418
|
-
|
|
419
|
-
❌ Too deep
|
|
420
|
-
GET /api/v1/publishers/1/authors/2/books/3/reviews/4
|
|
421
|
-
|
|
422
|
-
✅ Use query parameters instead
|
|
423
|
-
GET /api/v1/reviews?bookId=3
|
|
424
|
-
```
|
|
425
|
-
|
|
426
|
-
## API Versioning
|
|
427
|
-
|
|
428
|
-
```
|
|
429
|
-
✅ Always version from the start
|
|
430
|
-
/api/v1/books
|
|
431
|
-
/api/v2/books
|
|
432
|
-
|
|
433
|
-
❌ No version
|
|
434
|
-
/api/books
|
|
435
|
-
```
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
---
|
|
439
|
-
|
|
440
|
-
# API Pagination
|
|
441
|
-
|
|
442
|
-
## Always Paginate Collections
|
|
443
|
-
|
|
444
|
-
```typescript
|
|
445
|
-
// ✅ Paginated endpoint
|
|
446
|
-
app.get('/api/v1/books', async (req, res) => {
|
|
447
|
-
const page = parseInt(req.query.page as string) || 1;
|
|
448
|
-
const limit = Math.min(parseInt(req.query.limit as string) || 20, 100);
|
|
449
|
-
|
|
450
|
-
const { data, total } = await bookService.findAll({ page, limit });
|
|
451
|
-
|
|
452
|
-
res.json({
|
|
453
|
-
data,
|
|
454
|
-
pagination: {
|
|
455
|
-
page,
|
|
456
|
-
limit,
|
|
457
|
-
total,
|
|
458
|
-
totalPages: Math.ceil(total / limit),
|
|
459
|
-
hasNext: page * limit < total,
|
|
460
|
-
hasPrevious: page > 1
|
|
461
|
-
}
|
|
462
|
-
});
|
|
463
|
-
});
|
|
464
|
-
```
|
|
465
|
-
|
|
466
|
-
## Offset-Based Pagination
|
|
467
|
-
|
|
468
|
-
```typescript
|
|
469
|
-
// Simple but has issues with large datasets
|
|
470
|
-
GET /api/v1/books?page=1&limit=20
|
|
471
|
-
GET /api/v1/books?page=2&limit=20
|
|
472
|
-
|
|
473
|
-
// Implementation
|
|
474
|
-
const getBooks = async (page: number, limit: number) => {
|
|
475
|
-
const offset = (page - 1) * limit;
|
|
476
|
-
|
|
477
|
-
const [data, total] = await Promise.all([
|
|
478
|
-
db.query('SELECT * FROM books ORDER BY id LIMIT ? OFFSET ?', [limit, offset]),
|
|
479
|
-
db.query('SELECT COUNT(*) FROM books')
|
|
480
|
-
]);
|
|
481
|
-
|
|
482
|
-
return { data, total };
|
|
483
|
-
};
|
|
484
|
-
```
|
|
485
|
-
|
|
486
|
-
## Cursor-Based Pagination
|
|
487
|
-
|
|
488
|
-
```typescript
|
|
489
|
-
// Better for large datasets and real-time data
|
|
490
|
-
GET /api/v1/books?cursor=eyJpZCI6MTIzfQ&limit=20
|
|
491
|
-
|
|
492
|
-
// Response includes next cursor
|
|
493
|
-
{
|
|
494
|
-
"data": [...],
|
|
495
|
-
"pagination": {
|
|
496
|
-
"nextCursor": "eyJpZCI6MTQzfQ",
|
|
497
|
-
"hasMore": true
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
// Implementation
|
|
502
|
-
const getBooks = async (cursor: string | null, limit: number) => {
|
|
503
|
-
let query = 'SELECT * FROM books';
|
|
504
|
-
|
|
505
|
-
if (cursor) {
|
|
506
|
-
const { id } = decodeCursor(cursor);
|
|
507
|
-
query += ` WHERE id > ${id}`;
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
query += ` ORDER BY id LIMIT ${limit + 1}`;
|
|
511
|
-
const data = await db.query(query);
|
|
512
|
-
|
|
513
|
-
const hasMore = data.length > limit;
|
|
514
|
-
const items = hasMore ? data.slice(0, limit) : data;
|
|
515
|
-
|
|
516
|
-
return {
|
|
517
|
-
data: items,
|
|
518
|
-
pagination: {
|
|
519
|
-
nextCursor: hasMore ? encodeCursor({ id: items[items.length - 1].id }) : null,
|
|
520
|
-
hasMore
|
|
521
|
-
}
|
|
522
|
-
};
|
|
523
|
-
};
|
|
524
|
-
```
|
|
525
|
-
|
|
526
|
-
## Keyset Pagination
|
|
527
|
-
|
|
528
|
-
```sql
|
|
529
|
-
-- Most efficient for large tables
|
|
530
|
-
-- First page
|
|
531
|
-
SELECT * FROM products
|
|
532
|
-
ORDER BY created_at DESC, id DESC
|
|
533
|
-
LIMIT 20;
|
|
534
|
-
|
|
535
|
-
-- Next page (using last item's values)
|
|
536
|
-
SELECT * FROM products
|
|
537
|
-
WHERE (created_at, id) < ('2024-01-15 10:00:00', 12345)
|
|
538
|
-
ORDER BY created_at DESC, id DESC
|
|
539
|
-
LIMIT 20;
|
|
540
|
-
```
|
|
541
|
-
|
|
542
|
-
## HATEOAS Links
|
|
543
|
-
|
|
544
|
-
```typescript
|
|
545
|
-
// Include navigation links
|
|
546
|
-
{
|
|
547
|
-
"data": [...],
|
|
548
|
-
"pagination": {
|
|
549
|
-
"page": 2,
|
|
550
|
-
"limit": 20,
|
|
551
|
-
"total": 150
|
|
552
|
-
},
|
|
553
|
-
"links": {
|
|
554
|
-
"self": "/api/v1/books?page=2&limit=20",
|
|
555
|
-
"first": "/api/v1/books?page=1&limit=20",
|
|
556
|
-
"prev": "/api/v1/books?page=1&limit=20",
|
|
557
|
-
"next": "/api/v1/books?page=3&limit=20",
|
|
558
|
-
"last": "/api/v1/books?page=8&limit=20"
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
```
|
|
562
|
-
|
|
563
|
-
## Pagination Best Practices
|
|
564
|
-
|
|
565
|
-
```typescript
|
|
566
|
-
// ✅ Set reasonable defaults and limits
|
|
567
|
-
const page = parseInt(req.query.page) || 1;
|
|
568
|
-
const limit = Math.min(parseInt(req.query.limit) || 20, 100);
|
|
569
|
-
|
|
570
|
-
// ✅ Include total count (when practical)
|
|
571
|
-
const total = await db.count('books');
|
|
572
|
-
|
|
573
|
-
// ✅ Use consistent response structure
|
|
574
|
-
{
|
|
575
|
-
"data": [],
|
|
576
|
-
"pagination": { ... }
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
// ❌ Don't return unlimited results
|
|
580
|
-
// ❌ Don't allow page < 1 or limit < 1
|
|
581
|
-
```
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
---
|
|
585
|
-
|
|
586
|
-
# API Versioning
|
|
587
|
-
|
|
588
|
-
## Versioning Strategies
|
|
589
|
-
|
|
590
|
-
### URL Path Versioning
|
|
591
|
-
```
|
|
592
|
-
GET /api/v1/users
|
|
593
|
-
GET /api/v2/users
|
|
594
|
-
```
|
|
595
|
-
|
|
596
|
-
### Header Versioning
|
|
597
|
-
```
|
|
598
|
-
GET /api/users
|
|
599
|
-
Accept: application/vnd.api+json; version=2
|
|
600
|
-
```
|
|
601
|
-
|
|
602
|
-
### Query Parameter
|
|
603
|
-
```
|
|
604
|
-
GET /api/users?version=2
|
|
605
|
-
```
|
|
606
|
-
|
|
607
|
-
## Implementation
|
|
608
|
-
|
|
609
|
-
```typescript
|
|
610
|
-
// URL path versioning
|
|
611
|
-
app.use('/api/v1', v1Router);
|
|
612
|
-
app.use('/api/v2', v2Router);
|
|
613
|
-
|
|
614
|
-
// Header versioning middleware
|
|
615
|
-
function versionMiddleware(req, res, next) {
|
|
616
|
-
const version = req.headers['api-version'] || '1';
|
|
617
|
-
req.apiVersion = parseInt(version);
|
|
618
|
-
next();
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
app.get('/users', versionMiddleware, (req, res) => {
|
|
622
|
-
if (req.apiVersion >= 2) {
|
|
623
|
-
return handleV2(req, res);
|
|
624
|
-
}
|
|
625
|
-
return handleV1(req, res);
|
|
626
|
-
});
|
|
627
|
-
```
|
|
628
|
-
|
|
629
|
-
## Deprecation Strategy
|
|
630
|
-
|
|
631
|
-
```typescript
|
|
632
|
-
// Add deprecation headers
|
|
633
|
-
res.setHeader('Deprecation', 'true');
|
|
634
|
-
res.setHeader('Sunset', 'Sat, 01 Jan 2025 00:00:00 GMT');
|
|
635
|
-
res.setHeader('Link', '</api/v2/users>; rel="successor-version"');
|
|
636
|
-
```
|
|
637
|
-
|
|
638
|
-
## Best Practices
|
|
639
|
-
|
|
640
|
-
- Version from the start
|
|
641
|
-
- Support at least N-1 versions
|
|
642
|
-
- Document deprecation timeline
|
|
643
|
-
- Provide migration guides
|
|
644
|
-
- Use semantic versioning for breaking changes
|
|
645
|
-
- Consider backwards-compatible changes first
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
---
|
|
649
|
-
*Generated by aicgen*
|