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
package/docs/core/routes.md
DELETED
|
@@ -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.
|