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.
@@ -1,720 +0,0 @@
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 Examples
34
-
35
- For common routing patterns and examples, see [Shared Examples](./examples.md).
36
-
37
- ## HTTP Methods
38
-
39
- YinzerFlow supports all standard HTTP methods with automatic route registration:
40
-
41
- ### GET Routes
42
- ```typescript
43
- app.get('/api/users', (ctx) => {
44
- return { users: ['John', 'Jane'] };
45
- });
46
-
47
- // Automatically registers corresponding HEAD route
48
- app.get('/api/users/:id', (ctx) => {
49
- const userId = ctx.request.params.id;
50
- return { userId, name: 'John Doe' };
51
- });
52
- ```
53
-
54
- **Note**: GET routes automatically register corresponding HEAD routes for HTTP compliance. The HEAD route uses the same handler but returns only headers, no body.
55
-
56
- ### POST Routes
57
- ```typescript
58
- app.post('/api/users', (ctx) => {
59
- const userData = ctx.request.body;
60
- return { message: 'User created', data: userData };
61
- });
62
- ```
63
-
64
- ### PUT Routes
65
- ```typescript
66
- app.put('/api/users/:id', (ctx) => {
67
- const userId = ctx.request.params.id;
68
- const updateData = ctx.request.body;
69
- return { message: 'User updated', userId, data: updateData };
70
- });
71
- ```
72
-
73
- ### PATCH Routes
74
- ```typescript
75
- app.patch('/api/users/:id', (ctx) => {
76
- const userId = ctx.request.params.id;
77
- const partialData = ctx.request.body;
78
- return { message: 'User partially updated', userId, data: partialData };
79
- });
80
- ```
81
-
82
- ### DELETE Routes
83
- ```typescript
84
- app.delete('/api/users/:id', (ctx) => {
85
- const userId = ctx.request.params.id;
86
- return { message: 'User deleted', userId };
87
- });
88
- ```
89
-
90
- ### OPTIONS Routes
91
- ```typescript
92
- app.options('/api/users', (ctx) => {
93
- ctx.response.addHeaders({
94
- 'Allow': 'GET, POST, PUT, DELETE, OPTIONS'
95
- });
96
- return { message: 'Available methods' };
97
- });
98
- ```
99
-
100
- ## Route Parameters
101
-
102
- 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:
103
-
104
- ```typescript
105
- // Single parameter
106
- app.get('/users/:id', ({ request }) => {
107
- const userId = request.params.id;
108
- return { userId };
109
- });
110
-
111
- // Multiple parameters
112
- app.get('/users/:id/posts/:postId', ({ request }) => {
113
- const { id, postId } = request.params;
114
- return { userId: id, postId };
115
- });
116
-
117
- // Nested parameters
118
- app.get('/api/v1/users/:userId/orders/:orderId/items/:itemId', ({ request }) => {
119
- const { userId, orderId, itemId } = request.params;
120
- return { userId, orderId, itemId };
121
- });
122
- ```
123
-
124
- ### Parameter Validation
125
-
126
- YinzerFlow automatically validates route parameters to ensure they are unique:
127
-
128
- ```typescript
129
- // ❌ This will throw an error - duplicate parameter names
130
- app.get('/users/:id/posts/:id', ({ request }) => {
131
- // Error: "Route /users/:id/posts/:id has duplicate parameter names: id"
132
- });
133
-
134
- // ✅ This is correct - unique parameter names
135
- app.get('/users/:userId/posts/:postId', ({ request }) => {
136
- const { userId, postId } = request.params;
137
- return { userId, postId };
138
- });
139
- ```
140
-
141
- **Parameter naming best practices:**
142
- - Use descriptive names: `:userId` instead of `:id`
143
- - Be specific: `:postId` instead of `:id`
144
- - Use consistent naming conventions
145
- - Avoid generic names that could cause confusion
146
-
147
- ## Context State
148
-
149
- YinzerFlow provides a powerful context state system that allows you to store and share custom data throughout the request lifecycle. This is perfect for authentication, middleware data, request-scoped variables, and custom context information.
150
-
151
- ### Quick Reference
152
-
153
- The context state is accessible via `ctx.state` and can store any data you need:
154
-
155
- ```typescript
156
- app.get('/api/users', async (ctx) => {
157
- // Store custom data in state
158
- ctx.state.user = { id: 1, name: 'John' };
159
- ctx.state.requestId = generateRequestId();
160
-
161
- // Access the data later
162
- console.log(ctx.state.user.name); // "John"
163
- console.log(ctx.state.requestId); // "req-123"
164
-
165
- return { users: ['John', 'Jane'] };
166
- });
167
- ```
168
-
169
- ### Middleware Integration
170
-
171
- State is perfect for middleware that needs to pass data to route handlers:
172
-
173
- ```typescript
174
- // Authentication middleware
175
- const authMiddleware: HandlerCallback = async (ctx) => {
176
- const token = ctx.request.headers.authorization;
177
- const user = await validateToken(token);
178
-
179
- // Store user data in state for route handlers
180
- ctx.state.user = user;
181
- ctx.state.permissions = await getUserPermissions(user.id);
182
- };
183
-
184
- // Route that uses the authenticated state
185
- app.get('/api/admin/users', authMiddleware, async (ctx) => {
186
- const { user, permissions } = ctx.state;
187
-
188
- if (!permissions.includes('admin')) {
189
- throw new Error('Insufficient permissions');
190
- }
191
-
192
- return { message: 'Admin access granted', user };
193
- });
194
- ```
195
-
196
- ### Route Group State
197
-
198
- State can be shared across route groups and inherited by nested routes:
199
-
200
- ```typescript
201
- app.group('/api/v1', (api) => {
202
- api.beforeAll([async (ctx) => {
203
- ctx.state.apiVersion = 'v1';
204
- ctx.state.environment = process.env.NODE_ENV;
205
- }]);
206
-
207
- api.group('/admin', (admin) => {
208
- admin.beforeAll([async (ctx) => {
209
- ctx.state.requiresAuth = true;
210
- ctx.state.adminOnly = true;
211
- }]);
212
-
213
- // Routes inherit all state from parent groups
214
- admin.get('/users', async (ctx) => {
215
- const { apiVersion, environment, requiresAuth, adminOnly } = ctx.state;
216
- return { users: ['Admin1', 'Admin2'] };
217
- });
218
- });
219
- });
220
- ```
221
-
222
- **📚 For comprehensive Context State documentation including TypeScript patterns, security considerations, and best practices, see [Context State Documentation](./context.md#context-state).**
223
-
224
- ## Query Parameters
225
-
226
- URL query strings are automatically parsed and available in `ctx.request.query`:
227
-
228
- ```typescript
229
- app.get('/api/search', ({ request }) => {
230
- const { q, limit, page, sort } = request.query;
231
-
232
- return {
233
- search: q,
234
- limit: parseInt(limit || '10'),
235
- page: parseInt(page || '1'),
236
- sort: sort || 'name'
237
- };
238
- });
239
-
240
- // Example: GET /api/search?q=test&limit=20&page=2&sort=date
241
- ```
242
-
243
- ## Request Body
244
-
245
- Request bodies are automatically parsed based on Content-Type:
246
-
247
- ```typescript
248
- // JSON body
249
- app.post('/api/users', ({ request }) => {
250
- const userData = request.body; // Automatically parsed JSON
251
- return { message: 'User created', data: userData };
252
- });
253
-
254
- // Form data
255
- app.post('/api/contact', ({ request }) => {
256
- const formData = request.body; // Automatically parsed form data
257
- return { message: 'Contact form submitted', data: formData };
258
- });
259
-
260
- // File uploads
261
- app.post('/api/upload', ({ request }) => {
262
- const files = request.body; // Automatically parsed file data
263
- return { message: 'Files uploaded', files };
264
- });
265
- ```
266
-
267
- ## Route Hooks
268
-
269
- YinzerFlow provides a powerful hook system for middleware and request/response processing:
270
-
271
- ### Inline Route Hooks
272
- ```typescript
273
- app.post(
274
- "/api/users/:id",
275
- ({ request, response }) => {
276
- const userId = request.params.id;
277
- const userData = request.body;
278
-
279
- return {
280
- message: 'User updated',
281
- userId,
282
- data: userData
283
- };
284
- },
285
- {
286
- beforeHooks: [
287
- ({ request, response }) => {
288
- // Authentication check
289
- const token = request.headers['authorization'];
290
- if (!token) {
291
- response.setStatusCode(401);
292
- return { error: 'Unauthorized' };
293
- }
294
- console.log('User authenticated');
295
- },
296
- ({ request }) => {
297
- // Log request
298
- console.log(`Updating user ${request.params.id}`);
299
- }
300
- ],
301
- afterHooks: [
302
- ({ request, response }) => {
303
- // Add response headers
304
- response.addHeaders({
305
- 'X-User-ID': request.params.id,
306
- 'X-Updated-At': new Date().toISOString()
307
- });
308
- },
309
- () => {
310
- // Log response
311
- console.log('User update completed');
312
- }
313
- ],
314
- }
315
- );
316
- ```
317
-
318
- ### Global Hooks
319
- ```typescript
320
- // Before all routes
321
- app.beforeAll([
322
- ({ request, response }) => {
323
- // Global authentication
324
- const token = request.headers['authorization'];
325
- if (!token && request.path.startsWith('/api/')) {
326
- response.setStatusCode(401);
327
- return { error: 'Authentication required' };
328
- }
329
- },
330
- ({ request }) => {
331
- // Global logging
332
- console.log(`${request.method} ${request.path}`);
333
- }
334
- ]);
335
-
336
- // After all routes
337
- app.afterAll([
338
- ({ response }) => {
339
- // Global response headers
340
- response.addHeaders({
341
- 'X-Response-Time': Date.now().toString(),
342
- 'X-Powered-By': 'YinzerFlow'
343
- });
344
- }
345
- ]);
346
-
347
- // Global hooks with options for selective execution
348
- app.beforeAll([
349
- ({ request, response }) => {
350
- // Authentication hook - only for API routes
351
- const token = request.headers['authorization'];
352
- if (!token && request.path.startsWith('/api/')) {
353
- response.setStatusCode(401);
354
- return { error: 'Authentication required' };
355
- }
356
- }
357
- ], {
358
- routesToInclude: ['/api/users', '/api/posts'], // Only run on specific routes
359
- routesToExclude: ['/api/health'] // Skip health check endpoint
360
- });
361
-
362
- app.afterAll([
363
- ({ request }) => {
364
- // Logging hook - exclude sensitive routes
365
- console.log(`${request.method} ${request.path}`);
366
- }
367
- ], {
368
- routesToExclude: ['/api/admin', '/api/secret'] // Skip logging for admin/secret routes
369
- });
370
- ```
371
-
372
- ### Hook Execution Order
373
-
374
- Global hooks are executed in the following order:
375
- 1. **beforeAll hooks** (global before hooks)
376
- 2. **Group beforeHooks** (if route is in a group)
377
- 3. **Route beforeHooks** (route-specific before hooks)
378
- 4. **Route handler** (the actual route handler)
379
- 5. **Route afterHooks** (route-specific after hooks)
380
- 6. **Group afterHooks** (if route is in a group)
381
- 7. **afterAll hooks** (global after hooks)
382
-
383
- ## Route Groups
384
-
385
- Route groups allow you to organize related routes under a common prefix and apply shared middleware or hooks to all routes within the group. Groups can be nested to create hierarchical route structures.
386
-
387
- ### Basic Group Usage
388
-
389
- ```typescript
390
- app.group('/api/v1', (group) => {
391
- group.get('/users', ({ response }) => {
392
- return { users: ['John', 'Jane'] };
393
- });
394
-
395
- group.post('/users', ({ request }) => {
396
- const userData = request.body;
397
- return { message: 'User created', data: userData };
398
- });
399
-
400
- group.get('/users/:id', ({ request }) => {
401
- const userId = request.params.id;
402
- return { userId, name: 'John Doe' };
403
- });
404
- }, {
405
- beforeHooks: [
406
- ({ request }) => {
407
- // API version logging
408
- console.log('API v1 request');
409
- }
410
- ]
411
- });
412
- ```
413
-
414
- ### Nested Groups
415
-
416
- Groups can be nested to create hierarchical route structures like `/api/v1/admin/users`:
417
-
418
- ```typescript
419
- app.group('/api/v1', (api) => {
420
- // /api/v1/users
421
- api.group('/users', (users) => {
422
- users.get('/', ({ response }) => {
423
- return { users: ['John', 'Jane'] };
424
- });
425
-
426
- users.get('/:id', ({ request }) => {
427
- const userId = request.params.id;
428
- return { userId, name: 'John Doe' };
429
- });
430
- });
431
-
432
- // /api/v1/admin
433
- api.group('/admin', (admin) => {
434
- // /api/v1/admin/users
435
- admin.group('/users', (adminUsers) => {
436
- adminUsers.get('/', ({ response }) => {
437
- return { adminUsers: ['Admin1', 'Admin2'] };
438
- });
439
-
440
- adminUsers.post('/', ({ request }) => {
441
- const userData = request.body;
442
- return { message: 'Admin user created', data: userData };
443
- });
444
- }, {
445
- beforeHooks: [
446
- ({ request }) => {
447
- // Admin authentication check
448
- if (!request.headers.authorization) {
449
- throw new Error('Unauthorized');
450
- }
451
- }
452
- ]
453
- });
454
- }, {
455
- beforeHooks: [
456
- ({ request }) => {
457
- // Admin role verification
458
- console.log('Admin route accessed');
459
- }
460
- ]
461
- });
462
- }, {
463
- beforeHooks: [
464
- ({ request }) => {
465
- // Global API logging
466
- console.log(`API request to: ${request.url}`);
467
- }
468
- ]
469
- });
470
- ```
471
-
472
- ### Hook Inheritance
473
-
474
- Hooks are inherited and merged from parent groups to child groups:
475
-
476
- ```typescript
477
- app.group('/api', (api) => {
478
- // This group inherits the 'api' beforeHooks
479
-
480
- api.group('/v1', (v1) => {
481
- // This group inherits both 'api' and 'v1' beforeHooks
482
-
483
- v1.group('/admin', (admin) => {
484
- // This group inherits 'api', 'v1', and 'admin' beforeHooks
485
-
486
- admin.get('/dashboard', ({ response }) => {
487
- // All three beforeHooks will execute in order
488
- return { message: 'Admin dashboard' };
489
- });
490
- }, {
491
- beforeHooks: [
492
- ({ request }) => {
493
- // Admin-specific hook
494
- console.log('Admin route accessed');
495
- }
496
- ]
497
- });
498
- }, {
499
- beforeHooks: [
500
- ({ request }) => {
501
- // Version-specific hook
502
- console.log('API v1 accessed');
503
- }
504
- ]
505
- });
506
- }, {
507
- beforeHooks: [
508
- ({ request }) => {
509
- // Global API hook
510
- console.log('API request');
511
- }
512
- ]
513
- });
514
- ```
515
-
516
- ### Group Options
517
-
518
- Groups support the same options as individual routes:
519
-
520
- ```typescript
521
- app.group('/api/v1', (group) => {
522
- group.get('/public', ({ response }) => {
523
- return { message: 'Public endpoint' };
524
- });
525
-
526
- group.get('/private', ({ response }) => {
527
- return { message: 'Private endpoint' };
528
- });
529
- }, {
530
- beforeHooks: [
531
- ({ request }) => {
532
- // Global group hooks
533
- console.log('API v1 request');
534
- }
535
- ],
536
- afterHooks: [
537
- ({ response }) => {
538
- // Global group after hooks
539
- response.headers.set('X-API-Version', 'v1');
540
- }
541
- ]
542
- });
543
- ```
544
-
545
- ### Group Hook Merging
546
-
547
- Hooks are merged in the following order:
548
- 1. **Group beforeHooks** (executed first)
549
- 2. **Route beforeHooks** (executed second)
550
- 3. **Route handler** (executed third)
551
- 4. **Route afterHooks** (executed fourth)
552
- 5. **Group afterHooks** (executed last)
553
-
554
- This allows for flexible middleware composition where group hooks provide shared functionality and route hooks handle specific logic.
555
-
556
- ## Separate Route Files
557
-
558
- Organize routes into separate files for better maintainability:
559
-
560
- ### routes/users.ts
561
- ```typescript
562
- import type { YinzerFlow } from 'yinzerflow';
563
- import { userHandlers } from '@app/handlers/users.ts';
564
-
565
- /**
566
- * Register user-related routes on the main app instance
567
- */
568
- export const registerUserRoutes = (app: YinzerFlow) => {
569
- /**
570
- * Get all users
571
- */
572
- app.get('/api/users', userHandlers.getAllUsers);
573
-
574
- /**
575
- * Get user by ID
576
- */
577
- app.get('/api/users/:id', userHandlers.getUserById);
578
-
579
- /**
580
- * Create new user
581
- */
582
- app.post('/api/users', userHandlers.createUser);
583
-
584
- /**
585
- * Update user
586
- */
587
- app.put('/api/users/:id', userHandlers.updateUser);
588
-
589
- /**
590
- * Delete user
591
- */
592
- app.delete('/api/users/:id', userHandlers.deleteUser);
593
- };
594
- ```
595
-
596
- ### routes/orders.ts
597
- ```typescript
598
- import type { YinzerFlow } from 'yinzerflow';
599
- import { orderHandlers } from '@app/handlers/orders.ts';
600
-
601
- /**
602
- * Register order-related routes on the main app instance
603
- */
604
- export const registerOrderRoutes = (app: YinzerFlow) => {
605
- /**
606
- * Get all orders for a user
607
- */
608
- app.get('/api/users/:userId/orders', orderHandlers.getUserOrders);
609
-
610
- /**
611
- * Get specific order
612
- */
613
- app.get('/api/orders/:orderId', orderHandlers.getOrderById);
614
-
615
- /**
616
- * Create new order
617
- */
618
- app.post('/api/orders', orderHandlers.createOrder);
619
-
620
- /**
621
- * Update order status
622
- */
623
- app.patch('/api/orders/:orderId/status', orderHandlers.updateOrderStatus);
624
- };
625
- ```
626
-
627
- ### Main application file
628
- ```typescript
629
- import { YinzerFlow } from 'yinzerflow';
630
- import { registerUserRoutes } from './routes/users.ts';
631
- import { registerOrderRoutes } from './routes/orders.ts';
632
-
633
- const app = new YinzerFlow({ port: 3000 });
634
-
635
- // Register route modules
636
- registerUserRoutes(app);
637
- registerOrderRoutes(app);
638
-
639
- // Start the server
640
- await app.listen();
641
- ```
642
-
643
- ## Error Handling
644
-
645
- YinzerFlow provides comprehensive error handling with automatic error catching and custom error handlers. The framework automatically catches all errors thrown in route handlers, hooks, and middleware.
646
-
647
- For detailed error handling documentation including advanced patterns, security considerations, and best practices, see [Error Handling Documentation](./error-handling.md).
648
-
649
- ## HandlerCallback Interface
650
-
651
- The `HandlerCallback` interface provides type safety for route handlers:
652
-
653
- ```typescript
654
- import type { HandlerCallback } from 'yinzerflow';
655
-
656
- // Basic handler
657
- const basicHandler: HandlerCallback = ({ request, response }) => {
658
- return { message: 'Hello world' };
659
- };
660
-
661
- // Typed handler with custom body and response types
662
- interface UserBody {
663
- name: string;
664
- email: string;
665
- }
666
-
667
- interface UserResponse {
668
- id: string;
669
- name: string;
670
- email: string;
671
- createdAt: string;
672
- }
673
-
674
- const createUserHandler: HandlerCallback<{
675
- body: UserBody;
676
- response: UserResponse;
677
- query: Record<string, string>;
678
- params: Record<string, string>;
679
- }> = ({ request }) => {
680
- const userData = request.body; // Typed as UserBody
681
- const userId = request.params.id;
682
- const includeProfile = request.query.include_profile;
683
-
684
- return {
685
- id: 'user-123',
686
- name: userData.name,
687
- email: userData.email,
688
- createdAt: new Date().toISOString()
689
- }; // Typed as UserResponse
690
- };
691
- ```
692
-
693
- The interface accepts these optional generic parameters:
694
- - `body?: unknown` - Type for request body
695
- - `response?: unknown` - Type for response body
696
- - `query?: Record<string, string>` - Type for query parameters
697
- - `params?: Record<string, string>` - Type for route parameters
698
-
699
- Using the interface is optional but encouraged for better type safety and developer experience.
700
-
701
- ## Security Considerations
702
-
703
- YinzerFlow implements several security measures to prevent common routing vulnerabilities. For detailed security documentation, see:
704
-
705
- - **[Body Parsing Security](../security/body-parsing.md)** - Protection against DoS attacks and malicious payloads
706
- - **[CORS Security](../security/cors.md)** - Cross-origin request validation and protection
707
- - **[IP Security](../security/ip-security.md)** - Client IP validation and spoofing protection
708
-
709
- ### Key Security Features
710
-
711
- - **Route Parameter Validation**: Automatic validation and sanitization prevents injection attacks
712
- - **Path Traversal Protection**: Comprehensive path normalization prevents directory traversal
713
- - **Query Parameter Sanitization**: Built-in sanitization with configurable limits
714
- - **Request Body Validation**: Size limits and content validation prevent DoS attacks
715
- - **Hook Execution Security**: Isolated hook execution with graceful error handling
716
- - **Route Collision Prevention**: Automatic detection and prevention of route conflicts
717
- - **Method Validation**: Strict HTTP method validation against RFC specifications
718
- - **Response Header Security**: Automatic security headers and validation
719
-
720
- These security measures ensure YinzerFlow's routing implementation follows security best practices and prevents common attack vectors while maintaining HTTP compliance and performance.