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/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.