yinzerflow 0.2.4 → 0.2.6
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/docs/advanced-configuration-options.md +116 -0
- package/docs/request.md +67 -18
- package/docs/routes.md +656 -0
- package/docs/start-here.md +178 -0
- package/example/README.md +11 -0
- package/example/app/handlers/example.ts +30 -0
- package/example/app/index.ts +110 -0
- package/example/app/routes/example.ts +18 -0
- package/example/app/util/customLogger.ts +166 -0
- package/example/docker-compose.yml +22 -0
- package/example/package.json +16 -0
- package/example/tsconfig.json +54 -0
- package/index.d.ts +14 -7
- package/index.js +10 -10
- package/index.js.map +5 -5
- package/package.json +2 -2
- package/docs/start-here.MD +0 -116
package/docs/routes.md
ADDED
|
@@ -0,0 +1,656 @@
|
|
|
1
|
+
# Routes
|
|
2
|
+
|
|
3
|
+
YinzerFlow provides a powerful and flexible routing system with support for HTTP methods, route parameters, query parameters, hooks, and route grouping for organized API development.
|
|
4
|
+
|
|
5
|
+
## Configuration
|
|
6
|
+
|
|
7
|
+
Route registration is automatically enabled and requires no configuration for basic usage:
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import { YinzerFlow } from 'yinzerflow';
|
|
11
|
+
|
|
12
|
+
const app = new YinzerFlow({ port: 3000 });
|
|
13
|
+
|
|
14
|
+
// Routes are registered directly on the app instance
|
|
15
|
+
app.get('/api/users', (ctx) => {
|
|
16
|
+
return { message: 'Users endpoint' };
|
|
17
|
+
});
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Configuration Options
|
|
21
|
+
|
|
22
|
+
YinzerFlow's routing system includes built-in security and performance optimizations:
|
|
23
|
+
|
|
24
|
+
| Feature | Default | Description |
|
|
25
|
+
|---------|---------|-------------|
|
|
26
|
+
| **Route Parameters** | Automatic | Dynamic URL segments like `/users/:id` |
|
|
27
|
+
| **Query Parameters** | Automatic | URL query strings like `?search=test&limit=10` |
|
|
28
|
+
| **Request Body** | Automatic | JSON, form data, and file uploads |
|
|
29
|
+
| **Response Headers** | Automatic | Security headers and custom headers |
|
|
30
|
+
| **Hook System** | Optional | Before/after hooks for middleware |
|
|
31
|
+
| **Route Groups** | Optional | Organized route prefixes and shared hooks |
|
|
32
|
+
|
|
33
|
+
## Basic Example
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import { YinzerFlow } from 'yinzerflow';
|
|
37
|
+
|
|
38
|
+
const app = new YinzerFlow({ port: 3000 });
|
|
39
|
+
|
|
40
|
+
// Simple GET route
|
|
41
|
+
app.get('/api/health', ({ response }) => {
|
|
42
|
+
return { status: 'healthy', timestamp: new Date().toISOString() };
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// POST route with body parsing
|
|
46
|
+
app.post('/api/users', ({ request }) => {
|
|
47
|
+
const userData = request.body;
|
|
48
|
+
const clientIp = request.ipAddress;
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
message: 'User created',
|
|
52
|
+
data: userData,
|
|
53
|
+
clientIp
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Route with parameters
|
|
58
|
+
app.get('/api/users/:id', ({ request }) => {
|
|
59
|
+
const userId = request.params.id;
|
|
60
|
+
const includeProfile = request.query.include_profile;
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
userId,
|
|
64
|
+
includeProfile,
|
|
65
|
+
message: 'User details retrieved'
|
|
66
|
+
};
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
For detailed information about the request and response objects, see [Request Documentation](./request.md) and [Response Documentation](./response.md).
|
|
71
|
+
|
|
72
|
+
## Common Use Cases
|
|
73
|
+
|
|
74
|
+
- **API Endpoints**: Create RESTful APIs with proper HTTP methods and status codes
|
|
75
|
+
- **File Uploads**: Handle multipart form data with automatic parsing and validation
|
|
76
|
+
- **Authentication**: Implement middleware hooks for token validation and user sessions
|
|
77
|
+
- **Rate Limiting**: Add before hooks to limit request frequency and prevent abuse
|
|
78
|
+
- **Logging**: Use after hooks to log request/response data for monitoring
|
|
79
|
+
- **CORS Handling**: Configure cross-origin requests with proper headers and preflight handling
|
|
80
|
+
- **Route Organization**: Group related routes with shared prefixes and middleware
|
|
81
|
+
|
|
82
|
+
## HTTP Methods
|
|
83
|
+
|
|
84
|
+
YinzerFlow supports all standard HTTP methods with automatic route registration:
|
|
85
|
+
|
|
86
|
+
### GET Routes
|
|
87
|
+
```typescript
|
|
88
|
+
app.get('/api/users', (ctx) => {
|
|
89
|
+
return { users: ['John', 'Jane'] };
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Automatically registers corresponding HEAD route
|
|
93
|
+
app.get('/api/users/:id', (ctx) => {
|
|
94
|
+
const userId = ctx.request.params.id;
|
|
95
|
+
return { userId, name: 'John Doe' };
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Note**: GET routes automatically register corresponding HEAD routes for HTTP compliance. The HEAD route uses the same handler but returns only headers, no body.
|
|
100
|
+
|
|
101
|
+
### POST Routes
|
|
102
|
+
```typescript
|
|
103
|
+
app.post('/api/users', (ctx) => {
|
|
104
|
+
const userData = ctx.request.body;
|
|
105
|
+
return { message: 'User created', data: userData };
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### PUT Routes
|
|
110
|
+
```typescript
|
|
111
|
+
app.put('/api/users/:id', (ctx) => {
|
|
112
|
+
const userId = ctx.request.params.id;
|
|
113
|
+
const updateData = ctx.request.body;
|
|
114
|
+
return { message: 'User updated', userId, data: updateData };
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### PATCH Routes
|
|
119
|
+
```typescript
|
|
120
|
+
app.patch('/api/users/:id', (ctx) => {
|
|
121
|
+
const userId = ctx.request.params.id;
|
|
122
|
+
const partialData = ctx.request.body;
|
|
123
|
+
return { message: 'User partially updated', userId, data: partialData };
|
|
124
|
+
});
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### DELETE Routes
|
|
128
|
+
```typescript
|
|
129
|
+
app.delete('/api/users/:id', (ctx) => {
|
|
130
|
+
const userId = ctx.request.params.id;
|
|
131
|
+
return { message: 'User deleted', userId };
|
|
132
|
+
});
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### OPTIONS Routes
|
|
136
|
+
```typescript
|
|
137
|
+
app.options('/api/users', (ctx) => {
|
|
138
|
+
ctx.response.addHeaders({
|
|
139
|
+
'Allow': 'GET, POST, PUT, DELETE, OPTIONS'
|
|
140
|
+
});
|
|
141
|
+
return { message: 'Available methods' };
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Route Parameters
|
|
146
|
+
|
|
147
|
+
Dynamic URL segments are automatically parsed and available in `ctx.request.params`. **Parameter names must be unique within a route** to prevent conflicts and ensure clarity:
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
// Single parameter
|
|
151
|
+
app.get('/users/:id', ({ request }) => {
|
|
152
|
+
const userId = request.params.id;
|
|
153
|
+
return { userId };
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// Multiple parameters
|
|
157
|
+
app.get('/users/:id/posts/:postId', ({ request }) => {
|
|
158
|
+
const { id, postId } = request.params;
|
|
159
|
+
return { userId: id, postId };
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Nested parameters
|
|
163
|
+
app.get('/api/v1/users/:userId/orders/:orderId/items/:itemId', ({ request }) => {
|
|
164
|
+
const { userId, orderId, itemId } = request.params;
|
|
165
|
+
return { userId, orderId, itemId };
|
|
166
|
+
});
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Parameter Validation
|
|
170
|
+
|
|
171
|
+
YinzerFlow automatically validates route parameters to ensure they are unique:
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
// ❌ This will throw an error - duplicate parameter names
|
|
175
|
+
app.get('/users/:id/posts/:id', ({ request }) => {
|
|
176
|
+
// Error: "Route /users/:id/posts/:id has duplicate parameter names: id"
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// ✅ This is correct - unique parameter names
|
|
180
|
+
app.get('/users/:userId/posts/:postId', ({ request }) => {
|
|
181
|
+
const { userId, postId } = request.params;
|
|
182
|
+
return { userId, postId };
|
|
183
|
+
});
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
**Parameter naming best practices:**
|
|
187
|
+
- Use descriptive names: `:userId` instead of `:id`
|
|
188
|
+
- Be specific: `:postId` instead of `:id`
|
|
189
|
+
- Use consistent naming conventions
|
|
190
|
+
- Avoid generic names that could cause confusion
|
|
191
|
+
|
|
192
|
+
## Query Parameters
|
|
193
|
+
|
|
194
|
+
URL query strings are automatically parsed and available in `ctx.request.query`:
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
app.get('/api/search', ({ request }) => {
|
|
198
|
+
const { q, limit, page, sort } = request.query;
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
search: q,
|
|
202
|
+
limit: parseInt(limit || '10'),
|
|
203
|
+
page: parseInt(page || '1'),
|
|
204
|
+
sort: sort || 'name'
|
|
205
|
+
};
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// Example: GET /api/search?q=test&limit=20&page=2&sort=date
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Request Body
|
|
212
|
+
|
|
213
|
+
Request bodies are automatically parsed based on Content-Type:
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
// JSON body
|
|
217
|
+
app.post('/api/users', ({ request }) => {
|
|
218
|
+
const userData = request.body; // Automatically parsed JSON
|
|
219
|
+
return { message: 'User created', data: userData };
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// Form data
|
|
223
|
+
app.post('/api/contact', ({ request }) => {
|
|
224
|
+
const formData = request.body; // Automatically parsed form data
|
|
225
|
+
return { message: 'Contact form submitted', data: formData };
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// File uploads
|
|
229
|
+
app.post('/api/upload', ({ request }) => {
|
|
230
|
+
const files = request.body; // Automatically parsed file data
|
|
231
|
+
return { message: 'Files uploaded', files };
|
|
232
|
+
});
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## Route Hooks
|
|
236
|
+
|
|
237
|
+
YinzerFlow provides a powerful hook system for middleware and request/response processing:
|
|
238
|
+
|
|
239
|
+
### Inline Route Hooks
|
|
240
|
+
```typescript
|
|
241
|
+
app.post(
|
|
242
|
+
"/api/users/:id",
|
|
243
|
+
({ request, response }) => {
|
|
244
|
+
const userId = request.params.id;
|
|
245
|
+
const userData = request.body;
|
|
246
|
+
|
|
247
|
+
return {
|
|
248
|
+
message: 'User updated',
|
|
249
|
+
userId,
|
|
250
|
+
data: userData
|
|
251
|
+
};
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
beforeHooks: [
|
|
255
|
+
({ request, response }) => {
|
|
256
|
+
// Authentication check
|
|
257
|
+
const token = request.headers['authorization'];
|
|
258
|
+
if (!token) {
|
|
259
|
+
response.setStatusCode(401);
|
|
260
|
+
return { error: 'Unauthorized' };
|
|
261
|
+
}
|
|
262
|
+
console.log('User authenticated');
|
|
263
|
+
},
|
|
264
|
+
({ request }) => {
|
|
265
|
+
// Log request
|
|
266
|
+
console.log(`Updating user ${request.params.id}`);
|
|
267
|
+
}
|
|
268
|
+
],
|
|
269
|
+
afterHooks: [
|
|
270
|
+
({ request, response }) => {
|
|
271
|
+
// Add response headers
|
|
272
|
+
response.addHeaders({
|
|
273
|
+
'X-User-ID': request.params.id,
|
|
274
|
+
'X-Updated-At': new Date().toISOString()
|
|
275
|
+
});
|
|
276
|
+
},
|
|
277
|
+
() => {
|
|
278
|
+
// Log response
|
|
279
|
+
console.log('User update completed');
|
|
280
|
+
}
|
|
281
|
+
],
|
|
282
|
+
}
|
|
283
|
+
);
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### Global Hooks
|
|
287
|
+
```typescript
|
|
288
|
+
// Before all routes
|
|
289
|
+
app.beforeAll([
|
|
290
|
+
({ request, response }) => {
|
|
291
|
+
// Global authentication
|
|
292
|
+
const token = request.headers['authorization'];
|
|
293
|
+
if (!token && request.path.startsWith('/api/')) {
|
|
294
|
+
response.setStatusCode(401);
|
|
295
|
+
return { error: 'Authentication required' };
|
|
296
|
+
}
|
|
297
|
+
},
|
|
298
|
+
({ request }) => {
|
|
299
|
+
// Global logging
|
|
300
|
+
console.log(`${request.method} ${request.path}`);
|
|
301
|
+
}
|
|
302
|
+
]);
|
|
303
|
+
|
|
304
|
+
// After all routes
|
|
305
|
+
app.afterAll([
|
|
306
|
+
({ response }) => {
|
|
307
|
+
// Global response headers
|
|
308
|
+
response.addHeaders({
|
|
309
|
+
'X-Response-Time': Date.now().toString(),
|
|
310
|
+
'X-Powered-By': 'YinzerFlow'
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
]);
|
|
314
|
+
|
|
315
|
+
// Global hooks with options for selective execution
|
|
316
|
+
app.beforeAll([
|
|
317
|
+
({ request, response }) => {
|
|
318
|
+
// Authentication hook - only for API routes
|
|
319
|
+
const token = request.headers['authorization'];
|
|
320
|
+
if (!token && request.path.startsWith('/api/')) {
|
|
321
|
+
response.setStatusCode(401);
|
|
322
|
+
return { error: 'Authentication required' };
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
], {
|
|
326
|
+
routesToInclude: ['/api/users', '/api/posts'], // Only run on specific routes
|
|
327
|
+
routesToExclude: ['/api/health'] // Skip health check endpoint
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
app.afterAll([
|
|
331
|
+
({ request }) => {
|
|
332
|
+
// Logging hook - exclude sensitive routes
|
|
333
|
+
console.log(`${request.method} ${request.path}`);
|
|
334
|
+
}
|
|
335
|
+
], {
|
|
336
|
+
routesToExclude: ['/api/admin', '/api/secret'] // Skip logging for admin/secret routes
|
|
337
|
+
});
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### Hook Execution Order
|
|
341
|
+
|
|
342
|
+
Global hooks are executed in the following order:
|
|
343
|
+
1. **beforeAll hooks** (global before hooks)
|
|
344
|
+
2. **Group beforeHooks** (if route is in a group)
|
|
345
|
+
3. **Route beforeHooks** (route-specific before hooks)
|
|
346
|
+
4. **Route handler** (the actual route handler)
|
|
347
|
+
5. **Route afterHooks** (route-specific after hooks)
|
|
348
|
+
6. **Group afterHooks** (if route is in a group)
|
|
349
|
+
7. **afterAll hooks** (global after hooks)
|
|
350
|
+
|
|
351
|
+
## Route Groups
|
|
352
|
+
|
|
353
|
+
Organize related routes with shared prefixes and hooks. Groups automatically merge hooks from the group level with individual route hooks:
|
|
354
|
+
|
|
355
|
+
```typescript
|
|
356
|
+
// API v1 routes with authentication
|
|
357
|
+
app.group('/api/v1', (group) => {
|
|
358
|
+
group.get('/users', ({ response }) => {
|
|
359
|
+
return { users: ['John', 'Jane'] };
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
group.post('/users', ({ request }) => {
|
|
363
|
+
const userData = request.body;
|
|
364
|
+
return { message: 'User created', data: userData };
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
group.get('/users/:id', ({ request }) => {
|
|
368
|
+
const userId = request.params.id;
|
|
369
|
+
return { userId, name: 'John Doe' };
|
|
370
|
+
});
|
|
371
|
+
}, {
|
|
372
|
+
beforeHooks: [
|
|
373
|
+
({ request }) => {
|
|
374
|
+
// API version logging
|
|
375
|
+
console.log('API v1 request');
|
|
376
|
+
}
|
|
377
|
+
]
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// Admin routes with admin authentication
|
|
381
|
+
app.group('/admin', (group) => {
|
|
382
|
+
group.get('/dashboard', ({ response }) => {
|
|
383
|
+
return { message: 'Admin dashboard' };
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
group.post('/settings', ({ request }) => {
|
|
387
|
+
const settings = request.body;
|
|
388
|
+
return { message: 'Settings updated', settings };
|
|
389
|
+
});
|
|
390
|
+
}, {
|
|
391
|
+
beforeHooks: [
|
|
392
|
+
({ request, response }) => {
|
|
393
|
+
// Admin authentication
|
|
394
|
+
const adminToken = request.headers['x-admin-token'];
|
|
395
|
+
if (!adminToken) {
|
|
396
|
+
response.setStatusCode(403);
|
|
397
|
+
return { error: 'Admin access required' };
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
]
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
// Nested groups with complex hook merging
|
|
404
|
+
app.group('/api/v1', (v1Group) => {
|
|
405
|
+
v1Group.group('/admin', (adminGroup) => {
|
|
406
|
+
adminGroup.get('/users', ({ response }) => {
|
|
407
|
+
return { message: 'Admin users endpoint' };
|
|
408
|
+
});
|
|
409
|
+
}, {
|
|
410
|
+
beforeHooks: [
|
|
411
|
+
() => {
|
|
412
|
+
// Admin-specific hook
|
|
413
|
+
console.log('Admin group hook');
|
|
414
|
+
}
|
|
415
|
+
]
|
|
416
|
+
});
|
|
417
|
+
}, {
|
|
418
|
+
beforeHooks: [
|
|
419
|
+
() => {
|
|
420
|
+
// API v1 hook
|
|
421
|
+
console.log('API v1 group hook');
|
|
422
|
+
}
|
|
423
|
+
]
|
|
424
|
+
});
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
### Group Hook Merging
|
|
428
|
+
|
|
429
|
+
Hooks are merged in the following order:
|
|
430
|
+
1. **Group beforeHooks** (executed first)
|
|
431
|
+
2. **Route beforeHooks** (executed second)
|
|
432
|
+
3. **Route handler** (executed third)
|
|
433
|
+
4. **Route afterHooks** (executed fourth)
|
|
434
|
+
5. **Group afterHooks** (executed last)
|
|
435
|
+
|
|
436
|
+
This allows for flexible middleware composition where group hooks provide shared functionality and route hooks handle specific logic.
|
|
437
|
+
|
|
438
|
+
## Separate Route Files
|
|
439
|
+
|
|
440
|
+
Organize routes into separate files for better maintainability:
|
|
441
|
+
|
|
442
|
+
### routes/users.ts
|
|
443
|
+
```typescript
|
|
444
|
+
import type { YinzerFlow } from 'yinzerflow';
|
|
445
|
+
import { userHandlers } from '@app/handlers/users.ts';
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Register user-related routes on the main app instance
|
|
449
|
+
*/
|
|
450
|
+
export const registerUserRoutes = (app: YinzerFlow) => {
|
|
451
|
+
/**
|
|
452
|
+
* Get all users
|
|
453
|
+
*/
|
|
454
|
+
app.get('/api/users', userHandlers.getAllUsers);
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Get user by ID
|
|
458
|
+
*/
|
|
459
|
+
app.get('/api/users/:id', userHandlers.getUserById);
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Create new user
|
|
463
|
+
*/
|
|
464
|
+
app.post('/api/users', userHandlers.createUser);
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Update user
|
|
468
|
+
*/
|
|
469
|
+
app.put('/api/users/:id', userHandlers.updateUser);
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Delete user
|
|
473
|
+
*/
|
|
474
|
+
app.delete('/api/users/:id', userHandlers.deleteUser);
|
|
475
|
+
};
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
### routes/orders.ts
|
|
479
|
+
```typescript
|
|
480
|
+
import type { YinzerFlow } from 'yinzerflow';
|
|
481
|
+
import { orderHandlers } from '@app/handlers/orders.ts';
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Register order-related routes on the main app instance
|
|
485
|
+
*/
|
|
486
|
+
export const registerOrderRoutes = (app: YinzerFlow) => {
|
|
487
|
+
/**
|
|
488
|
+
* Get all orders for a user
|
|
489
|
+
*/
|
|
490
|
+
app.get('/api/users/:userId/orders', orderHandlers.getUserOrders);
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Get specific order
|
|
494
|
+
*/
|
|
495
|
+
app.get('/api/orders/:orderId', orderHandlers.getOrderById);
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Create new order
|
|
499
|
+
*/
|
|
500
|
+
app.post('/api/orders', orderHandlers.createOrder);
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Update order status
|
|
504
|
+
*/
|
|
505
|
+
app.patch('/api/orders/:orderId/status', orderHandlers.updateOrderStatus);
|
|
506
|
+
};
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
### Main application file
|
|
510
|
+
```typescript
|
|
511
|
+
import { YinzerFlow } from 'yinzerflow';
|
|
512
|
+
import { registerUserRoutes } from './routes/users.ts';
|
|
513
|
+
import { registerOrderRoutes } from './routes/orders.ts';
|
|
514
|
+
|
|
515
|
+
const app = new YinzerFlow({ port: 3000 });
|
|
516
|
+
|
|
517
|
+
// Register route modules
|
|
518
|
+
registerUserRoutes(app);
|
|
519
|
+
registerOrderRoutes(app);
|
|
520
|
+
|
|
521
|
+
// Start the server
|
|
522
|
+
await app.listen();
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
## Error Handling
|
|
526
|
+
|
|
527
|
+
YinzerFlow provides comprehensive error handling for routes:
|
|
528
|
+
|
|
529
|
+
### Custom Error Handlers
|
|
530
|
+
```typescript
|
|
531
|
+
// Global error handler
|
|
532
|
+
app.onError(({ request, response }) => {
|
|
533
|
+
console.error('Unhandled error:', request.path);
|
|
534
|
+
response.setStatusCode(500);
|
|
535
|
+
return { error: 'Internal server error' };
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
// Custom not found handler
|
|
539
|
+
app.onNotFound(({ request, response }) => {
|
|
540
|
+
response.setStatusCode(404);
|
|
541
|
+
return {
|
|
542
|
+
error: 'Route not found',
|
|
543
|
+
path: request.path,
|
|
544
|
+
method: request.method
|
|
545
|
+
};
|
|
546
|
+
});
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
### Route-Specific Error Handling
|
|
550
|
+
```typescript
|
|
551
|
+
app.get('/api/users/:id', ({ request, response }) => {
|
|
552
|
+
try {
|
|
553
|
+
const userId = request.params.id;
|
|
554
|
+
|
|
555
|
+
// Simulate database lookup
|
|
556
|
+
if (userId === '999') {
|
|
557
|
+
throw new Error('User not found');
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
return { userId, name: 'John Doe' };
|
|
561
|
+
} catch (error) {
|
|
562
|
+
response.setStatusCode(404);
|
|
563
|
+
return { error: 'User not found' };
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
## HandlerCallback Interface
|
|
569
|
+
|
|
570
|
+
The `HandlerCallback` interface provides type safety for route handlers:
|
|
571
|
+
|
|
572
|
+
```typescript
|
|
573
|
+
import type { HandlerCallback } from 'yinzerflow';
|
|
574
|
+
|
|
575
|
+
// Basic handler
|
|
576
|
+
const basicHandler: HandlerCallback = ({ request, response }) => {
|
|
577
|
+
return { message: 'Hello world' };
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
// Typed handler with custom body and response types
|
|
581
|
+
interface UserBody {
|
|
582
|
+
name: string;
|
|
583
|
+
email: string;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
interface UserResponse {
|
|
587
|
+
id: string;
|
|
588
|
+
name: string;
|
|
589
|
+
email: string;
|
|
590
|
+
createdAt: string;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
const createUserHandler: HandlerCallback<{
|
|
594
|
+
body: UserBody;
|
|
595
|
+
response: UserResponse;
|
|
596
|
+
query: Record<string, string>;
|
|
597
|
+
params: Record<string, string>;
|
|
598
|
+
}> = ({ request }) => {
|
|
599
|
+
const userData = request.body; // Typed as UserBody
|
|
600
|
+
const userId = request.params.id;
|
|
601
|
+
const includeProfile = request.query.include_profile;
|
|
602
|
+
|
|
603
|
+
return {
|
|
604
|
+
id: 'user-123',
|
|
605
|
+
name: userData.name,
|
|
606
|
+
email: userData.email,
|
|
607
|
+
createdAt: new Date().toISOString()
|
|
608
|
+
}; // Typed as UserResponse
|
|
609
|
+
};
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
The interface accepts these optional generic parameters:
|
|
613
|
+
- `body?: unknown` - Type for request body
|
|
614
|
+
- `response?: unknown` - Type for response body
|
|
615
|
+
- `query?: Record<string, string>` - Type for query parameters
|
|
616
|
+
- `params?: Record<string, string>` - Type for route parameters
|
|
617
|
+
|
|
618
|
+
Using the interface is optional but encouraged for better type safety and developer experience.
|
|
619
|
+
|
|
620
|
+
## Security Considerations
|
|
621
|
+
|
|
622
|
+
YinzerFlow implements several security measures to prevent common routing vulnerabilities:
|
|
623
|
+
|
|
624
|
+
### 🛡️ Route Parameter Validation
|
|
625
|
+
- **Problem**: Malicious route parameters can cause injection attacks or bypass security controls
|
|
626
|
+
- **YinzerFlow Solution**: Automatic parameter validation and sanitization prevents injection attacks
|
|
627
|
+
|
|
628
|
+
### 🛡️ Path Traversal Protection
|
|
629
|
+
- **Problem**: Directory traversal attacks through URL paths can access unauthorized files
|
|
630
|
+
- **YinzerFlow Solution**: Comprehensive path normalization and validation prevents traversal attempts
|
|
631
|
+
|
|
632
|
+
### 🛡️ Query Parameter Sanitization
|
|
633
|
+
- **Problem**: Malicious query parameters can cause injection attacks or bypass validation
|
|
634
|
+
- **YinzerFlow Solution**: Automatic query parameter parsing with built-in sanitization
|
|
635
|
+
|
|
636
|
+
### 🛡️ Request Body Validation
|
|
637
|
+
- **Problem**: Malformed request bodies can cause parsing errors or security vulnerabilities
|
|
638
|
+
- **YinzerFlow Solution**: Comprehensive body parsing with size limits and validation - see [Body Parsing Documentation](./body-parsing.md)
|
|
639
|
+
|
|
640
|
+
### 🛡️ Hook Execution Security
|
|
641
|
+
- **Problem**: Malicious hooks can modify responses or bypass security controls
|
|
642
|
+
- **YinzerFlow Solution**: Hook execution is isolated and errors are handled gracefully
|
|
643
|
+
|
|
644
|
+
### 🛡️ Route Collision Prevention
|
|
645
|
+
- **Problem**: Duplicate routes can cause unexpected behavior or security bypasses
|
|
646
|
+
- **YinzerFlow Solution**: Automatic detection and prevention of route conflicts during registration
|
|
647
|
+
|
|
648
|
+
### 🛡️ Method Validation
|
|
649
|
+
- **Problem**: Invalid HTTP methods can cause parsing errors or security issues
|
|
650
|
+
- **YinzerFlow Solution**: Strict validation of HTTP methods against RFC specifications
|
|
651
|
+
|
|
652
|
+
### 🛡️ Response Header Security
|
|
653
|
+
- **Problem**: Malicious response headers can cause client-side vulnerabilities
|
|
654
|
+
- **YinzerFlow Solution**: Automatic security headers and header validation - see [Response Documentation](./response.md)
|
|
655
|
+
|
|
656
|
+
These security measures ensure YinzerFlow's routing implementation follows security best practices and prevents common attack vectors while maintaining HTTP compliance and performance.
|