yinzerflow 0.4.4 → 0.5.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 +31 -26
- package/docs/configuration/configuration.md +815 -0
- package/docs/core/core-concepts.md +801 -0
- package/docs/core/error-handling.md +391 -153
- package/docs/core/logging.md +426 -68
- package/docs/modules/body-parsing.md +561 -0
- package/docs/modules/cors.md +369 -0
- package/docs/modules/index.md +125 -0
- package/docs/modules/ip-security.md +280 -0
- package/docs/modules/rate-limiting.md +795 -0
- package/index.d.ts +278 -76
- package/index.js +18 -18
- package/index.js.map +17 -8
- package/package.json +5 -3
- package/docs/configuration/advanced-configuration-options.md +0 -302
- package/docs/configuration/configuration-patterns.md +0 -500
- package/docs/core/context.md +0 -230
- package/docs/core/examples.md +0 -444
- package/docs/core/request.md +0 -161
- package/docs/core/response.md +0 -212
- package/docs/core/routes.md +0 -720
- package/docs/quick-reference.md +0 -346
- package/docs/security/body-parsing.md +0 -296
- package/docs/security/cors.md +0 -189
- package/docs/security/ip-security.md +0 -234
- package/docs/security/security-overview.md +0 -282
- package/docs/start-here.md +0 -184
|
@@ -0,0 +1,801 @@
|
|
|
1
|
+
# 📖 Core Concepts
|
|
2
|
+
|
|
3
|
+
YinzerFlow's core concepts provide the foundation for building HTTP APIs. This guide covers the Context object, Request handling, Response control, Routing system, and Hooks - the essential building blocks for any YinzerFlow application.
|
|
4
|
+
|
|
5
|
+
For detailed configuration examples and patterns, see [Configuration Guide](../configuration/configuration.md).
|
|
6
|
+
|
|
7
|
+
# ⚙️ Usage
|
|
8
|
+
|
|
9
|
+
## 🔧 Context Object
|
|
10
|
+
|
|
11
|
+
The Context object is the central interface for all YinzerFlow route handlers and hooks. It provides access to request data, response controls, and maintains request-scoped state throughout the request lifecycle.
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { YinzerFlow } from 'yinzerflow';
|
|
15
|
+
|
|
16
|
+
const app = new YinzerFlow({ port: 3000 });
|
|
17
|
+
|
|
18
|
+
// Context is automatically provided to all handlers
|
|
19
|
+
app.get('/api/data', (ctx) => {
|
|
20
|
+
// Access request data
|
|
21
|
+
const { path, method, headers } = ctx.request;
|
|
22
|
+
|
|
23
|
+
// Control response
|
|
24
|
+
ctx.response.setStatusCode(200);
|
|
25
|
+
|
|
26
|
+
// Store custom state
|
|
27
|
+
ctx.state.user = { id: 1, name: 'John' };
|
|
28
|
+
|
|
29
|
+
return { message: 'Success' };
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### State Management
|
|
34
|
+
|
|
35
|
+
Request-scoped state data that persists throughout the request lifecycle.
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
app.get('/api/users', async (ctx) => {
|
|
39
|
+
// Store custom data in state
|
|
40
|
+
ctx.state.user = { id: 1, name: 'John' };
|
|
41
|
+
ctx.state.requestId = generateRequestId();
|
|
42
|
+
ctx.state.timestamp = Date.now();
|
|
43
|
+
|
|
44
|
+
// Access the data later
|
|
45
|
+
console.log(ctx.state.user.name); // "John"
|
|
46
|
+
console.log(ctx.state.requestId); // "req-123"
|
|
47
|
+
console.log(ctx.state.timestamp); // 1703123456789
|
|
48
|
+
|
|
49
|
+
return { users: ['John', 'Jane'] };
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## 🔧 Request Object
|
|
54
|
+
|
|
55
|
+
YinzerFlow provides a comprehensive request object containing parsed headers, body, query parameters, route parameters, and metadata with built-in security protections.
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
app.get('/api/users/:id', ({ request }) => {
|
|
59
|
+
// Access route parameters
|
|
60
|
+
const userId = request.params.id;
|
|
61
|
+
|
|
62
|
+
// Access query parameters
|
|
63
|
+
const includeProfile = request.query.include_profile;
|
|
64
|
+
|
|
65
|
+
// Access headers
|
|
66
|
+
const contentType = request.headers['content-type'];
|
|
67
|
+
const authorization = request.headers['authorization'];
|
|
68
|
+
|
|
69
|
+
// Access request body
|
|
70
|
+
const userData = request.body;
|
|
71
|
+
|
|
72
|
+
// Access raw body for manual parsing when needed
|
|
73
|
+
const rawBody = request.rawBody;
|
|
74
|
+
|
|
75
|
+
const clientIp = request.ipAddress;
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
message: 'Request processed successfully',
|
|
79
|
+
userId,
|
|
80
|
+
includeProfile,
|
|
81
|
+
contentType,
|
|
82
|
+
hasAuth: !!authorization,
|
|
83
|
+
receivedData: userData
|
|
84
|
+
};
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Type Safety
|
|
89
|
+
|
|
90
|
+
Request data is type-safe through TypeScript generics.
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
// Custom types for specific endpoints
|
|
94
|
+
interface UserCreateRequest extends HandlerCallbackGenerics {
|
|
95
|
+
body: { name: string; email: string; age: number };
|
|
96
|
+
query: { page: string; limit: string };
|
|
97
|
+
params: { id: string };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const createUser: HandlerCallback<UserCreateRequest> = async (ctx) => {
|
|
101
|
+
// Fully typed!
|
|
102
|
+
const { name, email, age } = ctx.request.body; // Type: { name: string; email: string; age: number }
|
|
103
|
+
const { page, limit } = ctx.request.query; // Type: { page: string; limit: string }
|
|
104
|
+
const { id } = ctx.request.params; // Type: { id: string }
|
|
105
|
+
|
|
106
|
+
return { success: true, user: { name, email, age, id } };
|
|
107
|
+
};
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## 🔧 Response Object
|
|
111
|
+
|
|
112
|
+
YinzerFlow provides a powerful response object for controlling HTTP responses with automatic content type detection, header validation, and built-in security protections.
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
app.get('/api/users/:id', ({ response }) => {
|
|
116
|
+
// Set successful status code
|
|
117
|
+
response.setStatusCode(200);
|
|
118
|
+
|
|
119
|
+
// Add custom headers
|
|
120
|
+
response.addHeaders({
|
|
121
|
+
'X-User-ID': userId,
|
|
122
|
+
'Cache-Control': 'max-age=3600',
|
|
123
|
+
'X-API-Version': '1.0'
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Return JSON response body (Content-Type automatically set)
|
|
127
|
+
return {
|
|
128
|
+
id: userId,
|
|
129
|
+
name: 'John Doe',
|
|
130
|
+
email: 'john@example.com',
|
|
131
|
+
timestamp: new Date().toISOString()
|
|
132
|
+
};
|
|
133
|
+
});
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Security Headers
|
|
137
|
+
|
|
138
|
+
Automatic security headers are added to every response by default.
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
// Security headers are automatically added
|
|
142
|
+
app.get('/api/data', ({ response }) => {
|
|
143
|
+
// Additional security headers can be added
|
|
144
|
+
response.addHeaders({
|
|
145
|
+
'X-Content-Type-Options': 'nosniff',
|
|
146
|
+
'X-Frame-Options': 'DENY',
|
|
147
|
+
'X-XSS-Protection': '1; mode=block'
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
return { message: 'Secure response' };
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## 🔧 Routing System
|
|
155
|
+
|
|
156
|
+
YinzerFlow provides a powerful and flexible routing system with support for HTTP methods, route parameters, query parameters, hooks, and route grouping.
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
// Basic route registration
|
|
160
|
+
app.get('/api/users', (ctx) => {
|
|
161
|
+
return { message: 'Users endpoint' };
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// Route parameters
|
|
165
|
+
app.get('/users/:id', ({ request }) => {
|
|
166
|
+
const userId = request.params.id;
|
|
167
|
+
return { userId };
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Multiple parameters
|
|
171
|
+
app.get('/users/:id/posts/:postId', ({ request }) => {
|
|
172
|
+
const { id, postId } = request.params;
|
|
173
|
+
return { userId: id, postId };
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Route groups with hooks
|
|
177
|
+
app.group('/api/v1', (api) => {
|
|
178
|
+
api.get('/users', () => ({ users: [] }));
|
|
179
|
+
api.post('/users', () => ({ created: true }));
|
|
180
|
+
}, {
|
|
181
|
+
beforeHooks: [
|
|
182
|
+
async (ctx) => {
|
|
183
|
+
ctx.state.apiVersion = 'v1';
|
|
184
|
+
}
|
|
185
|
+
],
|
|
186
|
+
afterHooks: [
|
|
187
|
+
async (ctx) => {
|
|
188
|
+
ctx.response.addHeaders({
|
|
189
|
+
'X-API-Version': 'v1.0.0'
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
]
|
|
193
|
+
});
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Available HTTP Methods
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
app.get('/users', handler); // GET requests
|
|
200
|
+
app.post('/users', handler); // POST requests
|
|
201
|
+
app.put('/users/:id', handler); // PUT requests
|
|
202
|
+
app.patch('/users/:id', handler); // PATCH requests
|
|
203
|
+
app.delete('/users/:id', handler); // DELETE requests
|
|
204
|
+
app.head('/users', handler); // HEAD requests
|
|
205
|
+
app.options('/users', handler); // OPTIONS requests
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## 🔧 Hooks System
|
|
209
|
+
|
|
210
|
+
YinzerFlow provides a comprehensive hooks system for middleware, authentication, logging, and cross-cutting concerns. Hooks execute in a specific order and can be applied globally or to specific routes.
|
|
211
|
+
|
|
212
|
+
### Global Hooks
|
|
213
|
+
|
|
214
|
+
Global hooks run for every request and are perfect for authentication, logging, and response modification.
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
// Global authentication hook
|
|
218
|
+
app.beforeAll([
|
|
219
|
+
async (ctx) => {
|
|
220
|
+
const token = ctx.request.headers.authorization;
|
|
221
|
+
if (token) {
|
|
222
|
+
const user = await validateToken(token);
|
|
223
|
+
ctx.state.user = user;
|
|
224
|
+
ctx.state.isAuthenticated = true;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
]);
|
|
228
|
+
|
|
229
|
+
// Global logging hook
|
|
230
|
+
app.beforeAll([
|
|
231
|
+
async (ctx) => {
|
|
232
|
+
ctx.state.requestId = generateRequestId();
|
|
233
|
+
ctx.state.startTime = Date.now();
|
|
234
|
+
|
|
235
|
+
console.log(`Request ${ctx.state.requestId} to ${ctx.request.path}`);
|
|
236
|
+
}
|
|
237
|
+
], {
|
|
238
|
+
routesToExclude: ['/health', '/metrics'] // Skip logging for health checks
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// Global response modification hook
|
|
242
|
+
app.afterAll([
|
|
243
|
+
async (ctx) => {
|
|
244
|
+
// Add response headers based on state
|
|
245
|
+
if (ctx.state.requestId) {
|
|
246
|
+
ctx.response.addHeaders({
|
|
247
|
+
'X-Request-ID': ctx.state.requestId,
|
|
248
|
+
'X-Processing-Time': `${Date.now() - ctx.state.startTime}ms`
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Log response
|
|
253
|
+
console.log(`Request ${ctx.state.requestId} completed with status ${ctx.response.statusCode}`);
|
|
254
|
+
}
|
|
255
|
+
]);
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### Route-Specific Hooks
|
|
259
|
+
|
|
260
|
+
Route-specific hooks run only for specific routes and can be applied to individual routes or route groups.
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
// Individual route with hooks
|
|
264
|
+
app.get('/api/users/:id',
|
|
265
|
+
// beforeHooks
|
|
266
|
+
[
|
|
267
|
+
async (ctx) => {
|
|
268
|
+
ctx.state.requiresAuth = true;
|
|
269
|
+
console.log(`Accessing user ${ctx.request.params.id}`);
|
|
270
|
+
}
|
|
271
|
+
],
|
|
272
|
+
// route handler
|
|
273
|
+
async (ctx) => {
|
|
274
|
+
const user = await getUserById(ctx.request.params.id);
|
|
275
|
+
return { user };
|
|
276
|
+
},
|
|
277
|
+
// afterHooks
|
|
278
|
+
{
|
|
279
|
+
afterHooks: [
|
|
280
|
+
async (ctx) => {
|
|
281
|
+
console.log(`User ${ctx.request.params.id} accessed successfully`);
|
|
282
|
+
}
|
|
283
|
+
]
|
|
284
|
+
}
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
// Route group with shared hooks
|
|
288
|
+
app.group('/api/v1', (api) => {
|
|
289
|
+
api.get('/users', () => ({ users: [] }));
|
|
290
|
+
api.post('/users', () => ({ created: true }));
|
|
291
|
+
}, {
|
|
292
|
+
beforeHooks: [
|
|
293
|
+
async (ctx) => {
|
|
294
|
+
ctx.state.apiVersion = 'v1';
|
|
295
|
+
ctx.state.requiresAuth = true;
|
|
296
|
+
}
|
|
297
|
+
],
|
|
298
|
+
afterHooks: [
|
|
299
|
+
async (ctx) => {
|
|
300
|
+
ctx.response.addHeaders({
|
|
301
|
+
'X-API-Version': 'v1.0.0'
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
]
|
|
305
|
+
});
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Hook Execution Order
|
|
309
|
+
|
|
310
|
+
Hooks execute in a specific order for predictable behavior:
|
|
311
|
+
|
|
312
|
+
1. **Global beforeAll hooks** (in registration order)
|
|
313
|
+
2. **Group beforeHooks** (parent groups first, then child groups)
|
|
314
|
+
3. **Route-specific beforeHooks** (in registration order)
|
|
315
|
+
4. **Route handler**
|
|
316
|
+
5. **Route-specific afterHooks** (in registration order)
|
|
317
|
+
6. **Group afterHooks** (child groups first, then parent groups)
|
|
318
|
+
7. **Global afterAll hooks** (in registration order)
|
|
319
|
+
|
|
320
|
+
```typescript
|
|
321
|
+
// Example showing execution order
|
|
322
|
+
app.beforeAll([() => console.log('1. Global beforeAll')]);
|
|
323
|
+
app.beforeAll([() => console.log('2. Global beforeAll 2')]);
|
|
324
|
+
|
|
325
|
+
app.group('/api', (api) => {
|
|
326
|
+
api.get('/test',
|
|
327
|
+
[() => console.log('4. Route beforeHook')],
|
|
328
|
+
() => {
|
|
329
|
+
console.log('5. Route handler');
|
|
330
|
+
return { message: 'success' };
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
afterHooks: [() => console.log('6. Route afterHook')]
|
|
334
|
+
}
|
|
335
|
+
);
|
|
336
|
+
}, {
|
|
337
|
+
beforeHooks: [() => console.log('3. Group beforeHook')],
|
|
338
|
+
afterHooks: [() => console.log('7. Group afterHook')]
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
app.afterAll([() => console.log('8. Global afterAll')]);
|
|
342
|
+
app.afterAll([() => console.log('9. Global afterAll 2')]);
|
|
343
|
+
|
|
344
|
+
// Output order: 1, 2, 3, 4, 5, 6, 7, 8, 9
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### Hook Options
|
|
348
|
+
|
|
349
|
+
Hooks support configuration options for selective execution:
|
|
350
|
+
|
|
351
|
+
```typescript
|
|
352
|
+
// Hook options for selective execution
|
|
353
|
+
interface HookOptions {
|
|
354
|
+
routesToInclude?: string[]; // Only run for specific routes
|
|
355
|
+
routesToExclude?: string[]; // Skip for specific routes
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Example: Authentication hook that skips public routes
|
|
359
|
+
app.beforeAll([
|
|
360
|
+
async (ctx) => {
|
|
361
|
+
const token = ctx.request.headers.authorization;
|
|
362
|
+
if (!token) {
|
|
363
|
+
ctx.response.setStatusCode(401);
|
|
364
|
+
return { error: 'Authentication required' };
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const user = await validateToken(token);
|
|
368
|
+
ctx.state.user = user;
|
|
369
|
+
}
|
|
370
|
+
], {
|
|
371
|
+
routesToExclude: ['/health', '/metrics', '/public/*']
|
|
372
|
+
});
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
# ✨ Best Practices
|
|
376
|
+
|
|
377
|
+
- **Use TypeScript generics** for type-safe request/response data access
|
|
378
|
+
- **Validate request data** before processing
|
|
379
|
+
- **Set appropriate status codes** for different operations (200, 201, 404, 500)
|
|
380
|
+
- **Use meaningful headers** for caching, security, and API versioning
|
|
381
|
+
- **Organize routes with groups** - Group related endpoints together
|
|
382
|
+
- **Use hooks for shared logic** - Authentication, logging, validation
|
|
383
|
+
- **Keep state minimal** - only store data that's actually needed
|
|
384
|
+
- **Handle errors gracefully** - Use try-catch and proper error responses
|
|
385
|
+
|
|
386
|
+
# 💻 Examples
|
|
387
|
+
|
|
388
|
+
### Production API
|
|
389
|
+
|
|
390
|
+
**Use Case:** Secure API with comprehensive request/response handling and organized routing
|
|
391
|
+
|
|
392
|
+
**Description:** Production-ready implementation with TypeScript generics, authentication, validation, error handling, and organized route structure for maximum security and maintainability.
|
|
393
|
+
|
|
394
|
+
```typescript
|
|
395
|
+
import { YinzerFlow } from 'yinzerflow';
|
|
396
|
+
import type { HandlerCallback } from 'yinzerflow';
|
|
397
|
+
|
|
398
|
+
// Define custom context types
|
|
399
|
+
interface AuthContext extends HandlerCallbackGenerics {
|
|
400
|
+
body: { username: string; password: string };
|
|
401
|
+
response: { token: string; user: User };
|
|
402
|
+
state: {
|
|
403
|
+
user: User;
|
|
404
|
+
permissions: string[];
|
|
405
|
+
requestId: string;
|
|
406
|
+
session: Session;
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const app = new YinzerFlow({ port: 3000 });
|
|
411
|
+
|
|
412
|
+
// Global authentication middleware
|
|
413
|
+
const authMiddleware: HandlerCallback = async (ctx) => {
|
|
414
|
+
const token = ctx.request.headers.authorization;
|
|
415
|
+
if (!token) {
|
|
416
|
+
ctx.response.setStatusCode(401);
|
|
417
|
+
return { error: 'Authentication required' };
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const user = await validateToken(token);
|
|
421
|
+
ctx.state.user = user;
|
|
422
|
+
ctx.state.isAuthenticated = true;
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
// Global logging middleware
|
|
426
|
+
const loggingMiddleware: HandlerCallback = async (ctx) => {
|
|
427
|
+
ctx.state.requestId = generateRequestId();
|
|
428
|
+
ctx.state.startTime = Date.now();
|
|
429
|
+
|
|
430
|
+
console.log(`[${ctx.state.requestId}] ${ctx.request.method} ${ctx.request.path}`);
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
// Global hooks
|
|
434
|
+
app.beforeAll([loggingMiddleware]);
|
|
435
|
+
app.afterAll([
|
|
436
|
+
async (ctx) => {
|
|
437
|
+
const processingTime = Date.now() - ctx.state.startTime;
|
|
438
|
+
ctx.response.addHeaders({
|
|
439
|
+
'X-Request-ID': ctx.state.requestId,
|
|
440
|
+
'X-Processing-Time': `${processingTime}ms`
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
]);
|
|
444
|
+
|
|
445
|
+
// Custom error handler
|
|
446
|
+
app.onError(async (ctx, error) => {
|
|
447
|
+
console.error(`[${ctx.state.requestId}] Error:`, error);
|
|
448
|
+
|
|
449
|
+
ctx.response.setStatusCode(500);
|
|
450
|
+
return {
|
|
451
|
+
error: 'Internal server error',
|
|
452
|
+
requestId: ctx.state.requestId,
|
|
453
|
+
message: process.env.NODE_ENV === 'production' ? 'Something went wrong' : error.message
|
|
454
|
+
};
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
// API v1 routes
|
|
458
|
+
app.group('/api/v1', (api) => {
|
|
459
|
+
// Public routes
|
|
460
|
+
api.get('/health', async (ctx) => {
|
|
461
|
+
return { status: 'healthy', timestamp: new Date().toISOString() };
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
// User routes
|
|
465
|
+
api.group('/users', (users) => {
|
|
466
|
+
// Get all users
|
|
467
|
+
users.get('/', async (ctx) => {
|
|
468
|
+
const users = await getAllUsers();
|
|
469
|
+
return { users, count: users.length };
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
// Get user by ID
|
|
473
|
+
users.get('/:userId', async (ctx) => {
|
|
474
|
+
const { userId } = ctx.request.params;
|
|
475
|
+
|
|
476
|
+
// Validate UUID format
|
|
477
|
+
if (!isValidUUID(userId)) {
|
|
478
|
+
ctx.response.setStatusCode(400);
|
|
479
|
+
return { error: 'Invalid user ID format' };
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const user = await getUserById(userId);
|
|
483
|
+
if (!user) {
|
|
484
|
+
ctx.response.setStatusCode(404);
|
|
485
|
+
return { error: 'User not found' };
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
return { user };
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
// Create user
|
|
492
|
+
users.post('/', async (ctx) => {
|
|
493
|
+
const userData = ctx.request.body as { name: string; email: string };
|
|
494
|
+
|
|
495
|
+
// Validate required fields
|
|
496
|
+
if (!userData.name || !userData.email) {
|
|
497
|
+
ctx.response.setStatusCode(400);
|
|
498
|
+
return { error: 'Name and email are required' };
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
const user = await createUser(userData);
|
|
502
|
+
|
|
503
|
+
ctx.response.setStatusCode(201);
|
|
504
|
+
ctx.response.addHeaders({
|
|
505
|
+
'Location': `/api/v1/users/${user.id}`,
|
|
506
|
+
'X-User-ID': user.id,
|
|
507
|
+
'X-API-Version': 'v1.0.0',
|
|
508
|
+
'Cache-Control': 'no-cache'
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
return { user };
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
// Update user
|
|
515
|
+
users.put('/:userId', authMiddleware, async (ctx) => {
|
|
516
|
+
const { userId } = ctx.request.params;
|
|
517
|
+
const updateData = ctx.request.body as { name?: string; email?: string };
|
|
518
|
+
|
|
519
|
+
const user = await updateUser(userId, updateData);
|
|
520
|
+
if (!user) {
|
|
521
|
+
ctx.response.setStatusCode(404);
|
|
522
|
+
return { error: 'User not found' };
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
return { user };
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
// Delete user
|
|
529
|
+
users.delete('/:userId', authMiddleware, async (ctx) => {
|
|
530
|
+
const { userId } = ctx.request.params;
|
|
531
|
+
|
|
532
|
+
const deleted = await deleteUser(userId);
|
|
533
|
+
if (!deleted) {
|
|
534
|
+
ctx.response.setStatusCode(404);
|
|
535
|
+
return { error: 'User not found' };
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
ctx.response.setStatusCode(204);
|
|
539
|
+
return; // No content for successful deletion
|
|
540
|
+
});
|
|
541
|
+
});
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
await app.listen();
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
### Dev API
|
|
548
|
+
|
|
549
|
+
**Use Case:** Development server with extensive debugging and testing capabilities
|
|
550
|
+
|
|
551
|
+
**Description:** Development configuration with comprehensive logging, debug routes, and simplified error handling for easier development and testing.
|
|
552
|
+
|
|
553
|
+
```typescript
|
|
554
|
+
import { YinzerFlow } from 'yinzerflow';
|
|
555
|
+
|
|
556
|
+
const app = new YinzerFlow({ port: 3000 });
|
|
557
|
+
|
|
558
|
+
// Development logging middleware
|
|
559
|
+
const devLoggingMiddleware: HandlerCallback = async (ctx) => {
|
|
560
|
+
ctx.state.requestId = generateRequestId();
|
|
561
|
+
ctx.state.timestamp = Date.now();
|
|
562
|
+
|
|
563
|
+
console.log('=== Request Debug Info ===');
|
|
564
|
+
console.log(`Request ID: ${ctx.state.requestId}`);
|
|
565
|
+
console.log(`Method: ${ctx.request.method}`);
|
|
566
|
+
console.log(`Path: ${ctx.request.path}`);
|
|
567
|
+
console.log(`Headers:`, ctx.request.headers);
|
|
568
|
+
console.log(`Query:`, ctx.request.query);
|
|
569
|
+
console.log(`Params:`, ctx.request.params);
|
|
570
|
+
console.log(`Body:`, ctx.request.body);
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
// Global development hooks
|
|
574
|
+
app.beforeAll([devLoggingMiddleware]);
|
|
575
|
+
app.afterAll([
|
|
576
|
+
async (ctx) => {
|
|
577
|
+
const processingTime = Date.now() - ctx.state.timestamp;
|
|
578
|
+
|
|
579
|
+
ctx.response.addHeaders({
|
|
580
|
+
'X-Debug-Mode': 'true',
|
|
581
|
+
'X-Request-ID': ctx.state.requestId,
|
|
582
|
+
'X-Processing-Time': `${processingTime}ms`,
|
|
583
|
+
'X-Server-Version': 'YinzerFlow Dev'
|
|
584
|
+
});
|
|
585
|
+
|
|
586
|
+
console.log(`[${ctx.state.requestId}] Response sent in ${processingTime}ms`);
|
|
587
|
+
}
|
|
588
|
+
]);
|
|
589
|
+
|
|
590
|
+
// Debug routes
|
|
591
|
+
app.get('/debug', async (ctx) => {
|
|
592
|
+
return {
|
|
593
|
+
message: 'Debug information',
|
|
594
|
+
request: {
|
|
595
|
+
method: ctx.request.method,
|
|
596
|
+
path: ctx.request.path,
|
|
597
|
+
headers: ctx.request.headers,
|
|
598
|
+
query: ctx.request.query,
|
|
599
|
+
params: ctx.request.params,
|
|
600
|
+
body: ctx.request.body,
|
|
601
|
+
ipAddress: ctx.request.ipAddress
|
|
602
|
+
},
|
|
603
|
+
state: ctx.state,
|
|
604
|
+
timestamp: new Date().toISOString()
|
|
605
|
+
};
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
// Echo route for testing
|
|
609
|
+
app.post('/echo', async (ctx) => {
|
|
610
|
+
return {
|
|
611
|
+
message: 'Echo response',
|
|
612
|
+
received: {
|
|
613
|
+
method: ctx.request.method,
|
|
614
|
+
path: ctx.request.path,
|
|
615
|
+
headers: ctx.request.headers,
|
|
616
|
+
body: ctx.request.body,
|
|
617
|
+
query: ctx.request.query,
|
|
618
|
+
params: ctx.request.params
|
|
619
|
+
},
|
|
620
|
+
timestamp: new Date().toISOString()
|
|
621
|
+
};
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
// Test different HTTP methods
|
|
625
|
+
app.get('/test/:type', async (ctx) => {
|
|
626
|
+
const { type } = ctx.request.params;
|
|
627
|
+
|
|
628
|
+
switch (type) {
|
|
629
|
+
case 'json':
|
|
630
|
+
return { message: 'JSON response', type: 'object' };
|
|
631
|
+
case 'text':
|
|
632
|
+
return 'Plain text response';
|
|
633
|
+
case 'error':
|
|
634
|
+
throw new Error('Test error');
|
|
635
|
+
default:
|
|
636
|
+
return { message: 'Default response', availableTypes: ['json', 'text', 'error'] };
|
|
637
|
+
}
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
// Test route parameters
|
|
641
|
+
app.get('/test/params/:id/:name', async (ctx) => {
|
|
642
|
+
const { id, name } = ctx.request.params;
|
|
643
|
+
return { id, name, message: 'Parameters received' };
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
// Test query parameters
|
|
647
|
+
app.get('/test/query', async (ctx) => {
|
|
648
|
+
const { q, limit, page } = ctx.request.query;
|
|
649
|
+
return {
|
|
650
|
+
query: q,
|
|
651
|
+
limit: parseInt(limit || '10'),
|
|
652
|
+
page: parseInt(page || '1'),
|
|
653
|
+
message: 'Query parameters received'
|
|
654
|
+
};
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
// Test route groups
|
|
658
|
+
app.group('/test', (test) => {
|
|
659
|
+
test.get('/group', async (ctx) => {
|
|
660
|
+
return { message: 'Group route accessed' };
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
test.group('/nested', (nested) => {
|
|
664
|
+
nested.get('/route', async (ctx) => {
|
|
665
|
+
return { message: 'Nested group route accessed' };
|
|
666
|
+
});
|
|
667
|
+
});
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
await app.listen();
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
## 🚀 Performance Notes
|
|
674
|
+
|
|
675
|
+
- **Automatic parsing**: Request data is parsed once and cached
|
|
676
|
+
- **Type safety**: TypeScript generics provide compile-time type checking with zero runtime overhead
|
|
677
|
+
- **Memory efficiency**: State and request/response objects are automatically garbage collected after each request completes
|
|
678
|
+
- **O(1) route lookup**: Exact routes use hash map for constant time lookup
|
|
679
|
+
- **Pre-compiled regex**: Parameterized routes use pre-compiled regex for fast matching
|
|
680
|
+
- **Hook optimization**: Hooks are executed efficiently with minimal overhead
|
|
681
|
+
- **Request isolation**: Each request gets its own isolated context that's cleaned up automatically
|
|
682
|
+
|
|
683
|
+
## 🔒 Security Notes
|
|
684
|
+
|
|
685
|
+
YinzerFlow implements several security measures for core concepts:
|
|
686
|
+
|
|
687
|
+
### 🛡️ Request Isolation
|
|
688
|
+
- **Problem**: State data could leak between requests, allowing one user to access another user's data
|
|
689
|
+
- **YinzerFlow Solution**: Each request gets its own isolated state object that's automatically cleaned up
|
|
690
|
+
|
|
691
|
+
### 🛡️ RFC 7230 Header Compliance
|
|
692
|
+
- **Problem**: Invalid header names can bypass security filters and cause parsing inconsistencies
|
|
693
|
+
- **YinzerFlow Solution**: Strict validation against RFC 7230 specification - only valid header characters are allowed
|
|
694
|
+
|
|
695
|
+
### 🛡️ CRLF Injection Prevention
|
|
696
|
+
- **Problem**: Injecting carriage return (`\r`) or line feed (`\n`) characters in header values can allow attackers to inject additional headers or even HTTP response splitting attacks
|
|
697
|
+
- **YinzerFlow Solution**: Comprehensive validation in `validateResponseHeaderValue()` detects and blocks:
|
|
698
|
+
- CRLF characters (`\r`, `\n`) in header values
|
|
699
|
+
- Suspicious injection patterns like `value\r\nSet-Cookie:`
|
|
700
|
+
- Double CRLF patterns (`\r\n\r\n`) that could inject HTTP responses
|
|
701
|
+
- HTTP response line injection attempts
|
|
702
|
+
- All validation happens before headers are set, preventing injection attacks
|
|
703
|
+
|
|
704
|
+
### 🛡️ Route Parameter Validation
|
|
705
|
+
- **Problem**: Malicious route parameters can cause injection attacks or bypass security controls
|
|
706
|
+
- **YinzerFlow Solution**: Automatic parameter validation and sanitization prevents injection attacks
|
|
707
|
+
|
|
708
|
+
### 🛡️ Path Traversal Protection
|
|
709
|
+
- **Problem**: Directory traversal attacks through URL paths can access unauthorized files
|
|
710
|
+
- **YinzerFlow Solution**: Comprehensive path normalization and validation prevents traversal attempts
|
|
711
|
+
|
|
712
|
+
### 🛡️ Body Parsing Protection
|
|
713
|
+
- **Problem**: Malformed request bodies can cause parsing errors, memory exhaustion, or security vulnerabilities
|
|
714
|
+
- **YinzerFlow Solution**: Comprehensive body parsing security with size limits, validation, and protection against common attacks
|
|
715
|
+
|
|
716
|
+
### 🛡️ Automatic Security Headers
|
|
717
|
+
- **Problem**: Missing security headers leave applications vulnerable to clickjacking, MIME sniffing, and XSS attacks
|
|
718
|
+
- **YinzerFlow Solution**: Automatically adds essential security headers to every response unless explicitly overridden by the application
|
|
719
|
+
|
|
720
|
+
### 🛡️ Hook Execution Security
|
|
721
|
+
- **Problem**: Malicious hooks can cause security vulnerabilities or bypass controls
|
|
722
|
+
- **YinzerFlow Solution**: Isolated hook execution with graceful error handling and validation
|
|
723
|
+
|
|
724
|
+
## 🔧 Troubleshooting
|
|
725
|
+
|
|
726
|
+
### State Not Persisting Between Middleware
|
|
727
|
+
- **Problem**: State data set in middleware not available in route handler
|
|
728
|
+
- **Fix**: Ensure middleware runs before route handler
|
|
729
|
+
|
|
730
|
+
```typescript
|
|
731
|
+
// ❌ Wrong - middleware runs after route handler
|
|
732
|
+
app.get('/api/users', getUserHandler, authMiddleware);
|
|
733
|
+
|
|
734
|
+
// ✅ Correct - middleware runs before route handler
|
|
735
|
+
app.get('/api/users', authMiddleware, getUserHandler);
|
|
736
|
+
```
|
|
737
|
+
|
|
738
|
+
### Request Body Not Parsed
|
|
739
|
+
- **Problem**: `request.body` is undefined or not parsed correctly
|
|
740
|
+
- **Fix**: Check Content-Type header and body parser configuration
|
|
741
|
+
|
|
742
|
+
```typescript
|
|
743
|
+
// ❌ Wrong - missing Content-Type header
|
|
744
|
+
fetch('/api/users', {
|
|
745
|
+
method: 'POST',
|
|
746
|
+
body: JSON.stringify({ name: 'John' })
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
// ✅ Correct - include Content-Type header
|
|
750
|
+
fetch('/api/users', {
|
|
751
|
+
method: 'POST',
|
|
752
|
+
headers: { 'Content-Type': 'application/json' },
|
|
753
|
+
body: JSON.stringify({ name: 'John' })
|
|
754
|
+
});
|
|
755
|
+
```
|
|
756
|
+
|
|
757
|
+
### Response Headers Not Set
|
|
758
|
+
- **Problem**: Headers not appearing in response
|
|
759
|
+
- **Fix**: Check header names and values for validity
|
|
760
|
+
|
|
761
|
+
```typescript
|
|
762
|
+
// ❌ Wrong - invalid header name
|
|
763
|
+
response.addHeaders({ 'Invalid@Header': 'value' });
|
|
764
|
+
|
|
765
|
+
// ✅ Correct - valid header name
|
|
766
|
+
response.addHeaders({ 'X-Custom-Header': 'value' });
|
|
767
|
+
```
|
|
768
|
+
|
|
769
|
+
### Route Not Found
|
|
770
|
+
- **Problem**: Route not matching requests
|
|
771
|
+
- **Fix**: Check route path and parameter names
|
|
772
|
+
|
|
773
|
+
```typescript
|
|
774
|
+
// ❌ Wrong - route not found
|
|
775
|
+
app.get('/users/:id', handler);
|
|
776
|
+
// Request: GET /users/123/extra
|
|
777
|
+
|
|
778
|
+
// ✅ Correct - match the exact path
|
|
779
|
+
app.get('/users/:id', handler);
|
|
780
|
+
// Request: GET /users/123
|
|
781
|
+
```
|
|
782
|
+
|
|
783
|
+
### TypeScript Errors with Request Data
|
|
784
|
+
- **Problem**: TypeScript errors when accessing request properties
|
|
785
|
+
- **Fix**: Use proper generic typing
|
|
786
|
+
|
|
787
|
+
```typescript
|
|
788
|
+
// ❌ Wrong - no type safety
|
|
789
|
+
const handler: HandlerCallback = async (ctx) => {
|
|
790
|
+
const user = ctx.request.body; // Type: unknown
|
|
791
|
+
};
|
|
792
|
+
|
|
793
|
+
// ✅ Correct - type-safe request access
|
|
794
|
+
interface UserRequest extends HandlerCallbackGenerics {
|
|
795
|
+
body: { name: string; email: string };
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
const handler: HandlerCallback<UserRequest> = async (ctx) => {
|
|
799
|
+
const { name, email } = ctx.request.body; // Type: { name: string; email: string }
|
|
800
|
+
};
|
|
801
|
+
```
|