qhttpx 2.0.0 → 2.1.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 +29 -33
- package/dist/package.json +1 -1
- package/dist/src/core/metrics.d.ts +2 -1
- package/dist/src/core/metrics.js +9 -6
- package/dist/src/core/server.js +1 -8
- package/docs/API_REFERENCE.md +749 -0
- package/docs/MIGRATION_1.9_TO_2.0.md +495 -0
- package/docs/PRODUCTION_DEPLOYMENT.md +798 -0
- package/docs/SECURITY.md +876 -0
- package/package.json +1 -1
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
# QHttpX Migration Guide: 1.9.4 → 2.0.1
|
|
2
|
+
|
|
3
|
+
**Updated:** January 21, 2026
|
|
4
|
+
**Status:** Complete Breaking Changes Guide
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
QHttpX 2.0 introduces significant improvements in performance, security, and API consistency. While there are breaking changes, migration is straightforward (30 minutes for most projects).
|
|
11
|
+
|
|
12
|
+
## Quick Summary of Changes
|
|
13
|
+
|
|
14
|
+
| Aspect | 1.9.4 | 2.0+ | Migration |
|
|
15
|
+
|--------|-------|------|-----------|
|
|
16
|
+
| Response handling | `return {}` (implicit) | `json({}, 200)` (explicit) | Update all route handlers |
|
|
17
|
+
| Server startup | `app.listen(port, cb)` | `app.start(port)` (Promise) | Change callback style |
|
|
18
|
+
| Rate limiting | `app.rateLimit('strict')` | `app.security({rateLimit: {...}})` | Update config |
|
|
19
|
+
| Error handling | `throw app.error(401)` | Throw `HttpError` class | Update error creation |
|
|
20
|
+
| Middleware | Basic approach | Lifecycle hooks available | Optional improvements |
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Step-by-Step Migration
|
|
25
|
+
|
|
26
|
+
### Step 1: Update Response Handling
|
|
27
|
+
|
|
28
|
+
**1.9.4 (Implicit JSON):**
|
|
29
|
+
```typescript
|
|
30
|
+
app.get('/users', async ({ json }) => {
|
|
31
|
+
const users = await getUsers();
|
|
32
|
+
return { users }; // ❌ Implicit - status always 200
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
app.post('/users', async ({ body }) => {
|
|
36
|
+
const user = await createUser(body);
|
|
37
|
+
return { user, created: true }; // ❌ Always returns 200
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**2.0.1 (Explicit JSON):**
|
|
42
|
+
```typescript
|
|
43
|
+
app.get('/users', async ({ json }) => {
|
|
44
|
+
const users = await getUsers();
|
|
45
|
+
json({ users }, 200); // ✅ Explicit status
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
app.post('/users', async ({ body, json }) => {
|
|
49
|
+
const user = await createUser(body);
|
|
50
|
+
json({ user, created: true }, 201); // ✅ 201 Created
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Migration Command:** Search & Replace (with caution):
|
|
55
|
+
```
|
|
56
|
+
Find: return \({ (.*) }\);
|
|
57
|
+
Replace: json({ $1 }, 200);
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Then manually verify status codes** - change 201, 400, 404, 401 as needed.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
### Step 2: Update Server Startup
|
|
65
|
+
|
|
66
|
+
**1.9.4 (Callback-based):**
|
|
67
|
+
```typescript
|
|
68
|
+
app.listen(3000, () => {
|
|
69
|
+
console.log('Server running on http://localhost:3000');
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**2.0.1 (Promise-based):**
|
|
74
|
+
```typescript
|
|
75
|
+
// Option A: Using async/await
|
|
76
|
+
async function start() {
|
|
77
|
+
await app.start(3000);
|
|
78
|
+
console.log('Server running on http://localhost:3000');
|
|
79
|
+
}
|
|
80
|
+
start().catch(console.error);
|
|
81
|
+
|
|
82
|
+
// Option B: Using .then()
|
|
83
|
+
app.start(3000).then(() => {
|
|
84
|
+
console.log('Server running on http://localhost:3000');
|
|
85
|
+
}).catch(console.error);
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Migration Checklist:**
|
|
89
|
+
- [ ] Remove callback from `app.listen()`
|
|
90
|
+
- [ ] Change to `app.start()`
|
|
91
|
+
- [ ] Wrap in async function or use `.then()`
|
|
92
|
+
- [ ] Add error handling with `.catch()`
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
### Step 3: Update Rate Limiting Configuration
|
|
97
|
+
|
|
98
|
+
**1.9.4:**
|
|
99
|
+
```typescript
|
|
100
|
+
// String-based configuration
|
|
101
|
+
app.rateLimit('strict'); // Pre-defined
|
|
102
|
+
app.rateLimit('moderate'); // Limited customization
|
|
103
|
+
app.rateLimit('permissive');
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**2.0.1:**
|
|
107
|
+
```typescript
|
|
108
|
+
// Granular configuration
|
|
109
|
+
app.security({
|
|
110
|
+
rateLimit: {
|
|
111
|
+
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
112
|
+
max: 100, // 100 requests
|
|
113
|
+
standardHeaders: true, // Return RateLimit-* headers
|
|
114
|
+
legacyHeaders: false, // Disable X-RateLimit-* headers
|
|
115
|
+
trustProxy: true, // Trust X-Forwarded-For
|
|
116
|
+
keyGenerator: (ctx) => ctx.ip, // Custom key (IP by default)
|
|
117
|
+
handler: ({ json }) => json({ error: 'Rate limited' }, 429),
|
|
118
|
+
skip: (ctx) => ctx.ip === '127.0.0.1', // Skip localhost
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Common Presets:**
|
|
124
|
+
```typescript
|
|
125
|
+
// Strict (like 1.9.4)
|
|
126
|
+
app.security({
|
|
127
|
+
rateLimit: {
|
|
128
|
+
windowMs: 1 * 60 * 1000, // 1 minute
|
|
129
|
+
max: 10, // 10 requests
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Moderate
|
|
134
|
+
app.security({
|
|
135
|
+
rateLimit: {
|
|
136
|
+
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
137
|
+
max: 100, // 100 requests
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Permissive
|
|
142
|
+
app.security({
|
|
143
|
+
rateLimit: {
|
|
144
|
+
windowMs: 60 * 60 * 1000, // 1 hour
|
|
145
|
+
max: 1000, // 1000 requests
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
### Step 4: Update Error Handling
|
|
153
|
+
|
|
154
|
+
**1.9.4:**
|
|
155
|
+
```typescript
|
|
156
|
+
// ❌ This no longer exists
|
|
157
|
+
throw app.error(401, 'Unauthorized');
|
|
158
|
+
throw app.error(404, 'Not found');
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
**2.0.1 - Option A (Using HttpError):**
|
|
162
|
+
```typescript
|
|
163
|
+
import { HttpError } from 'qhttpx';
|
|
164
|
+
|
|
165
|
+
app.post('/protected', ({ json, httpError }) => {
|
|
166
|
+
if (!isAuthorized) {
|
|
167
|
+
throw httpError(401, 'Unauthorized', { code: 'AUTH_REQUIRED' });
|
|
168
|
+
}
|
|
169
|
+
json({ success: true }, 200);
|
|
170
|
+
});
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**2.0.1 - Option B (Direct JSON response):**
|
|
174
|
+
```typescript
|
|
175
|
+
app.post('/protected', ({ json }) => {
|
|
176
|
+
if (!isAuthorized) {
|
|
177
|
+
json({ error: 'Unauthorized', code: 'AUTH_REQUIRED' }, 401);
|
|
178
|
+
return; // Important: return to prevent further execution
|
|
179
|
+
}
|
|
180
|
+
json({ success: true }, 200);
|
|
181
|
+
});
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**2.0.1 - Option C (Using json() with error):**
|
|
185
|
+
```typescript
|
|
186
|
+
app.post('/protected', async ({ json }) => {
|
|
187
|
+
try {
|
|
188
|
+
const result = await protectedOperation();
|
|
189
|
+
json({ data: result }, 200);
|
|
190
|
+
} catch (error) {
|
|
191
|
+
if (error.code === 'AUTH_REQUIRED') {
|
|
192
|
+
json({ error: error.message, code: error.code }, 401);
|
|
193
|
+
} else {
|
|
194
|
+
json({ error: 'Internal server error' }, 500);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**Migration Path:**
|
|
201
|
+
1. Replace `app.error()` calls with `httpError()`
|
|
202
|
+
2. Or use `json({ error: msg }, status); return;`
|
|
203
|
+
3. Preferred: Use try-catch with explicit json responses
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
### Step 5: Update Configuration
|
|
208
|
+
|
|
209
|
+
**1.9.4:**
|
|
210
|
+
```typescript
|
|
211
|
+
app
|
|
212
|
+
.rateLimit('strict')
|
|
213
|
+
.production()
|
|
214
|
+
.compression()
|
|
215
|
+
.cors();
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
**2.0.1:**
|
|
219
|
+
```typescript
|
|
220
|
+
app
|
|
221
|
+
.fusion(true) // Enable Request Fusion
|
|
222
|
+
.metrics(true) // Enable metrics collection
|
|
223
|
+
.production() // Enable production optimizations
|
|
224
|
+
.security({
|
|
225
|
+
cors: {
|
|
226
|
+
origin: '*',
|
|
227
|
+
methods: ['GET', 'POST', 'PUT', 'DELETE'],
|
|
228
|
+
credentials: false,
|
|
229
|
+
},
|
|
230
|
+
rateLimit: {
|
|
231
|
+
windowMs: 15 * 60 * 1000,
|
|
232
|
+
max: 100,
|
|
233
|
+
},
|
|
234
|
+
headers: true, // Enable security headers
|
|
235
|
+
});
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
### Step 6: Update Middleware (Optional)
|
|
241
|
+
|
|
242
|
+
**1.9.4:**
|
|
243
|
+
```typescript
|
|
244
|
+
app.use(async ({ req, next }) => {
|
|
245
|
+
console.log(`${req.method} ${req.url}`);
|
|
246
|
+
if (next) await next();
|
|
247
|
+
});
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
**2.0.1 - Same approach still works:**
|
|
251
|
+
```typescript
|
|
252
|
+
app.use(async ({ req, next }) => {
|
|
253
|
+
console.log(`${req.method} ${req.url}`);
|
|
254
|
+
if (next) await next();
|
|
255
|
+
});
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
**2.0.1 - New lifecycle hooks (optional):**
|
|
259
|
+
```typescript
|
|
260
|
+
// Before handler
|
|
261
|
+
app.onRequest(({ req }) => {
|
|
262
|
+
console.log(`→ ${req.method} ${req.url}`);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// After handler
|
|
266
|
+
app.onResponse(({ req, status }) => {
|
|
267
|
+
console.log(`← ${req.method} ${req.url} ${status}`);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
// On error
|
|
271
|
+
app.onError(({ req, error }) => {
|
|
272
|
+
console.error(`❌ ${req.method} ${req.url}:`, error.message);
|
|
273
|
+
});
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## Full Migration Example
|
|
279
|
+
|
|
280
|
+
### Before (1.9.4):
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
import { app } from 'qhttpx';
|
|
284
|
+
|
|
285
|
+
app.rateLimit('strict').production();
|
|
286
|
+
|
|
287
|
+
app.get('/', ({ json }) => {
|
|
288
|
+
return { status: 'ok' };
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
app.post('/users', async ({ body, json }) => {
|
|
292
|
+
const { email, name } = body as any;
|
|
293
|
+
|
|
294
|
+
if (!email) {
|
|
295
|
+
throw app.error(400, 'Email required');
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const user = await createUser(email, name);
|
|
299
|
+
return { user };
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
app.onError(({ error, json }) => {
|
|
303
|
+
json({ error: error.message }, 500);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
app.listen(3000, () => {
|
|
307
|
+
console.log('Server running');
|
|
308
|
+
});
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### After (2.0.1):
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
import { app, HttpError } from 'qhttpx';
|
|
315
|
+
|
|
316
|
+
app
|
|
317
|
+
.production()
|
|
318
|
+
.security({
|
|
319
|
+
rateLimit: {
|
|
320
|
+
windowMs: 1 * 60 * 1000,
|
|
321
|
+
max: 10,
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
app.get('/', ({ json }) => {
|
|
326
|
+
json({ status: 'ok' }, 200);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
app.post('/users', async ({ body, json, httpError }) => {
|
|
330
|
+
const { email, name } = body as any;
|
|
331
|
+
|
|
332
|
+
if (!email) {
|
|
333
|
+
throw httpError(400, 'Email required');
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const user = await createUser(email, name);
|
|
337
|
+
json({ user }, 201); // 201 Created
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
app.onError(({ error, json }) => {
|
|
341
|
+
const status = error instanceof HttpError ? error.status : 500;
|
|
342
|
+
json({ error: error.message }, status);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
app.start(3000).then(() => {
|
|
346
|
+
console.log('Server running');
|
|
347
|
+
}).catch(console.error);
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## Automated Migration Script
|
|
353
|
+
|
|
354
|
+
**Create a script to help with common replacements:**
|
|
355
|
+
|
|
356
|
+
```bash
|
|
357
|
+
# Find all 'return {' statements in routes
|
|
358
|
+
grep -r "return {" src/routes/ | wc -l
|
|
359
|
+
|
|
360
|
+
# Find all 'app.error' calls
|
|
361
|
+
grep -r "app\.error" src/ | wc -l
|
|
362
|
+
|
|
363
|
+
# Find all 'app.listen' calls
|
|
364
|
+
grep -r "app\.listen" src/ | wc -l
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
---
|
|
368
|
+
|
|
369
|
+
## Common Pitfalls
|
|
370
|
+
|
|
371
|
+
### ❌ Pitfall 1: Forgetting to call json()
|
|
372
|
+
|
|
373
|
+
```typescript
|
|
374
|
+
// WRONG - Returns nothing
|
|
375
|
+
app.get('/users', ({ json }) => {
|
|
376
|
+
const users = getUsers();
|
|
377
|
+
// Missing: json(...)
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// RIGHT
|
|
381
|
+
app.get('/users', ({ json }) => {
|
|
382
|
+
const users = getUsers();
|
|
383
|
+
json({ users }, 200);
|
|
384
|
+
});
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
### ❌ Pitfall 2: Not returning after json() on error paths
|
|
388
|
+
|
|
389
|
+
```typescript
|
|
390
|
+
// WRONG - Continues execution
|
|
391
|
+
app.post('/users', ({ json }) => {
|
|
392
|
+
if (!isValid) {
|
|
393
|
+
json({ error: 'Invalid' }, 400);
|
|
394
|
+
// Missing: return
|
|
395
|
+
}
|
|
396
|
+
json({ success: true }, 200); // This still executes!
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
// RIGHT
|
|
400
|
+
app.post('/users', ({ json }) => {
|
|
401
|
+
if (!isValid) {
|
|
402
|
+
json({ error: 'Invalid' }, 400);
|
|
403
|
+
return; // Stop execution
|
|
404
|
+
}
|
|
405
|
+
json({ success: true }, 200);
|
|
406
|
+
});
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### ❌ Pitfall 3: Using old error creation
|
|
410
|
+
|
|
411
|
+
```typescript
|
|
412
|
+
// WRONG - app.error() doesn't exist
|
|
413
|
+
throw app.error(404, 'Not found');
|
|
414
|
+
|
|
415
|
+
// RIGHT
|
|
416
|
+
throw httpError(404, 'Not found');
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### ❌ Pitfall 4: Async/await with app.start()
|
|
420
|
+
|
|
421
|
+
```typescript
|
|
422
|
+
// WRONG - Not waiting for start
|
|
423
|
+
app.start(3000);
|
|
424
|
+
console.log('Running'); // May print before server is ready!
|
|
425
|
+
|
|
426
|
+
// RIGHT
|
|
427
|
+
async function start() {
|
|
428
|
+
await app.start(3000);
|
|
429
|
+
console.log('Running');
|
|
430
|
+
}
|
|
431
|
+
start();
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
---
|
|
435
|
+
|
|
436
|
+
## Testing After Migration
|
|
437
|
+
|
|
438
|
+
### Checklist:
|
|
439
|
+
|
|
440
|
+
- [ ] All routes return via `json()` or `send()`
|
|
441
|
+
- [ ] All error cases return proper status codes
|
|
442
|
+
- [ ] No more `app.error()` calls
|
|
443
|
+
- [ ] `app.listen()` changed to `app.start()`
|
|
444
|
+
- [ ] Error handler updated for new error types
|
|
445
|
+
- [ ] Rate limiting configured explicitly
|
|
446
|
+
- [ ] TypeScript compiles without errors
|
|
447
|
+
- [ ] All endpoints tested in Postman/curl
|
|
448
|
+
|
|
449
|
+
### Test Command:
|
|
450
|
+
|
|
451
|
+
```bash
|
|
452
|
+
# Compile TypeScript
|
|
453
|
+
npm run build
|
|
454
|
+
|
|
455
|
+
# Start server
|
|
456
|
+
npm run start
|
|
457
|
+
|
|
458
|
+
# Test endpoints
|
|
459
|
+
curl http://localhost:3000/
|
|
460
|
+
curl -X POST http://localhost:3000/users -H "Content-Type: application/json" -d '{"email":"test@example.com","name":"Test"}'
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
---
|
|
464
|
+
|
|
465
|
+
## Getting Help
|
|
466
|
+
|
|
467
|
+
1. **Check examples:** `QHttpX/examples/` directory
|
|
468
|
+
2. **Read API docs:** Check docs/ folder (v2.0.1+)
|
|
469
|
+
3. **GitHub Issues:** Report problems
|
|
470
|
+
4. **Community:** Ask on GitHub Discussions
|
|
471
|
+
|
|
472
|
+
---
|
|
473
|
+
|
|
474
|
+
## Version Support
|
|
475
|
+
|
|
476
|
+
- **v1.9.4:** No longer supported (use 2.0+)
|
|
477
|
+
- **v2.0.0+:** Current stable version
|
|
478
|
+
- **v2.1.0+:** Recommended (better docs, more features)
|
|
479
|
+
|
|
480
|
+
**Timeline:**
|
|
481
|
+
- v2.0.1: January 2026
|
|
482
|
+
- v2.1.0: February 2026 (docs + features)
|
|
483
|
+
- v3.0.0: Q2 2026 (stable API)
|
|
484
|
+
|
|
485
|
+
---
|
|
486
|
+
|
|
487
|
+
## Feedback
|
|
488
|
+
|
|
489
|
+
Found issues with this guide? Please:
|
|
490
|
+
1. Create a GitHub issue with "[DOCS]" prefix
|
|
491
|
+
2. Include your version number
|
|
492
|
+
3. Describe the unclear part
|
|
493
|
+
4. Suggest improvement
|
|
494
|
+
|
|
495
|
+
Thank you for helping make QHttpX better! 🙏
|