yinzerflow 0.1.18 → 0.2.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 +0 -296
- package/YinzerFlow.d.ts +565 -0
- package/YinzerFlow.js +24 -0
- package/YinzerFlow.js.map +42 -0
- package/docs/advanced-configuration-options.md +175 -0
- package/docs/body-parsing.md +294 -0
- package/docs/cors.md +187 -0
- package/docs/ip-security.md +232 -0
- package/docs/request.md +145 -0
- package/docs/response.md +251 -0
- package/docs/start-here.MD +116 -0
- package/example/index.ts +109 -53
- package/package.json +15 -17
- package/constants/index.d.ts +0 -86
- package/constants/index.js +0 -3
- package/constants/index.js.map +0 -13
- package/docs/README.md +0 -327
- package/docs/content-types.md +0 -390
- package/docs/error-handling.md +0 -266
- package/docs/file-parsers.md +0 -276
- package/docs/hooks.md +0 -289
- package/docs/routing.md +0 -204
- package/example/bun.lock +0 -866
- package/example/hooks/authentication.middleware.ts +0 -77
- package/example/package.json +0 -61
- package/example/routes/authentication.routes.ts +0 -243
- package/example/routes/content-types.ts +0 -116
- package/example/tsconfig.json +0 -32
- package/index.d.ts +0 -395
- package/index.js +0 -23
- package/index.js.map +0 -33
package/docs/README.md
DELETED
|
@@ -1,327 +0,0 @@
|
|
|
1
|
-
# YinzerFlow Documentation
|
|
2
|
-
|
|
3
|
-
Welcome to the YinzerFlow documentation! This directory contains comprehensive documentation for the YinzerFlow framework, a lightweight, modular HTTP server framework for Node.js built with TypeScript.
|
|
4
|
-
|
|
5
|
-
## Contents
|
|
6
|
-
|
|
7
|
-
- [Routing System](./routing.md) - Comprehensive guide to the routing system, including route definition, parameters, and groups.
|
|
8
|
-
- [Request Lifecycle Hooks](./hooks.md) - In-depth documentation of the hooks system (formerly middleware) for intercepting and modifying requests.
|
|
9
|
-
- [Content Type Handling](./content-types.md) - Working with different content types in requests and responses.
|
|
10
|
-
- [Error Handling](./error-handling.md) - Guide to handling errors at different levels in your application.
|
|
11
|
-
- [Connection Management](#connection-management) - Information about the built-in connection management system.
|
|
12
|
-
|
|
13
|
-
## Getting Started
|
|
14
|
-
|
|
15
|
-
If you're new to YinzerFlow, we recommend starting with the examples in the `/example` directory:
|
|
16
|
-
|
|
17
|
-
- [TypeScript Example](/example/typescript/README.md) - A type-safe server implementation in TypeScript
|
|
18
|
-
|
|
19
|
-
### Installation
|
|
20
|
-
|
|
21
|
-
You can install YinzerFlow using your preferred package manager:
|
|
22
|
-
|
|
23
|
-
```bash
|
|
24
|
-
# Using npm
|
|
25
|
-
npm install yinzerflow
|
|
26
|
-
|
|
27
|
-
# Using Yarn
|
|
28
|
-
yarn add yinzerflow
|
|
29
|
-
|
|
30
|
-
# Using Bun
|
|
31
|
-
bun add yinzerflow
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
### Quick Start
|
|
35
|
-
|
|
36
|
-
Here's a minimal example to get a server up and running:
|
|
37
|
-
|
|
38
|
-
```typescript
|
|
39
|
-
import { YinzerFlow } from 'yinzerflow';
|
|
40
|
-
|
|
41
|
-
// Create a new YinzerFlow instance
|
|
42
|
-
const app = new YinzerFlow({ port: 3000 });
|
|
43
|
-
|
|
44
|
-
// Add a simple route
|
|
45
|
-
app.get('/hello', () => {
|
|
46
|
-
return { message: 'Hello, World!' };
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
// Start the server
|
|
50
|
-
await app.listen();
|
|
51
|
-
const { port, isListening } = app.getStatus();
|
|
52
|
-
|
|
53
|
-
if (isListening) console.log(`Server running on http://localhost:${port}`);
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
## Core Concepts
|
|
57
|
-
|
|
58
|
-
### Routing
|
|
59
|
-
|
|
60
|
-
YinzerFlow provides a simple and intuitive routing system:
|
|
61
|
-
|
|
62
|
-
```typescript
|
|
63
|
-
// Basic route
|
|
64
|
-
app.get('/users', () => {
|
|
65
|
-
return { users: [] };
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
// Route with parameters
|
|
69
|
-
app.get('/users/:id', ({ request }) => {
|
|
70
|
-
const { id } = request.params;
|
|
71
|
-
return { user: { id, name: 'John Doe' } };
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
// Different HTTP methods
|
|
75
|
-
app.post('/users', ({ request }) => {
|
|
76
|
-
const newUser = request.body;
|
|
77
|
-
// Create user logic
|
|
78
|
-
return { success: true, user: newUser };
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
app.put('/users/:id', ({ request }) => {
|
|
82
|
-
const { id } = request.params;
|
|
83
|
-
const userData = request.body;
|
|
84
|
-
// Update user logic
|
|
85
|
-
return { success: true };
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
app.delete('/users/:id', ({ request }) => {
|
|
89
|
-
const { id } = request.params;
|
|
90
|
-
// Delete user logic
|
|
91
|
-
return { success: true };
|
|
92
|
-
});
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
For more detailed information about routing, see the [Routing System](./routing.md) documentation.
|
|
96
|
-
|
|
97
|
-
### Request Lifecycle Hooks
|
|
98
|
-
|
|
99
|
-
YinzerFlow provides a powerful hooks system (traditionally called middleware in other frameworks) that allows you to intercept and modify requests at various points in the request lifecycle:
|
|
100
|
-
|
|
101
|
-
```typescript
|
|
102
|
-
// Global hooks
|
|
103
|
-
app.beforeAll(({ request }) => {
|
|
104
|
-
console.log(`Request received: ${request.method} ${request.path}`);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
// Path-specific hooks
|
|
108
|
-
app.beforeAll(
|
|
109
|
-
({ request, response }) => {
|
|
110
|
-
const token = request.headers['authorization'];
|
|
111
|
-
if (!token) {
|
|
112
|
-
response.setStatus(401);
|
|
113
|
-
return { error: 'Authentication required' };
|
|
114
|
-
}
|
|
115
|
-
},
|
|
116
|
-
{ paths: ['/admin', '/profile'] }
|
|
117
|
-
);
|
|
118
|
-
|
|
119
|
-
// Excluded paths
|
|
120
|
-
app.beforeAll(
|
|
121
|
-
authHook,
|
|
122
|
-
{ paths: 'allButExcluded', excluded: ['/login', '/register'] }
|
|
123
|
-
);
|
|
124
|
-
|
|
125
|
-
// Method chaining
|
|
126
|
-
app
|
|
127
|
-
.beforeAll(logRequest)
|
|
128
|
-
.beforeAll(checkAuth, { paths: ['/admin/*'] });
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
For more detailed information about hooks, see the [Request Lifecycle Hooks](./hooks.md) documentation.
|
|
132
|
-
|
|
133
|
-
### Route Groups
|
|
134
|
-
|
|
135
|
-
Group related routes under a common prefix:
|
|
136
|
-
|
|
137
|
-
```typescript
|
|
138
|
-
// Define routes
|
|
139
|
-
const userRoutes = [
|
|
140
|
-
app.get('/profile', userProfileHandler),
|
|
141
|
-
app.put('/profile', updateProfileHandler),
|
|
142
|
-
app.get('/settings', userSettingsHandler)
|
|
143
|
-
];
|
|
144
|
-
|
|
145
|
-
// Apply group
|
|
146
|
-
app.group('/user', userRoutes, {
|
|
147
|
-
beforeGroup: authHook
|
|
148
|
-
});
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
### Content Type Handling
|
|
152
|
-
|
|
153
|
-
YinzerFlow automatically parses request bodies based on the Content-Type header:
|
|
154
|
-
|
|
155
|
-
```typescript
|
|
156
|
-
// JSON data
|
|
157
|
-
app.post('/api/json', ({ request, response }) => {
|
|
158
|
-
if (!isJsonData(request.body)) {
|
|
159
|
-
response.setStatus(400);
|
|
160
|
-
return { error: 'Expected JSON data' };
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const { name, email } = request.body;
|
|
164
|
-
return { success: true, data: { name, email } };
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
// File uploads
|
|
168
|
-
app.post('/api/upload', ({ request, response }) => {
|
|
169
|
-
if (!isMultipartFormData(request.body)) {
|
|
170
|
-
response.setStatus(400);
|
|
171
|
-
return { error: 'Expected multipart form data' };
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const { fields, files } = request.body;
|
|
175
|
-
return {
|
|
176
|
-
success: true,
|
|
177
|
-
message: `Received ${Object.keys(files).length} files`
|
|
178
|
-
};
|
|
179
|
-
});
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
For detailed information about working with different content types, see the [Content Type Handling](./content-types.md) documentation.
|
|
183
|
-
|
|
184
|
-
### Error Handling
|
|
185
|
-
|
|
186
|
-
Custom error handling for graceful error responses:
|
|
187
|
-
|
|
188
|
-
```typescript
|
|
189
|
-
const app = new YinzerFlow({
|
|
190
|
-
port: 3000,
|
|
191
|
-
errorHandler: ({ response }, error) => {
|
|
192
|
-
console.error('Error:', error);
|
|
193
|
-
response.setStatus(500);
|
|
194
|
-
return {
|
|
195
|
-
success: false,
|
|
196
|
-
message: 'An unexpected error occurred',
|
|
197
|
-
timestamp: new Date().toISOString()
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
|
-
```
|
|
202
|
-
|
|
203
|
-
### Event-Based Architecture
|
|
204
|
-
|
|
205
|
-
YinzerFlow uses an event-based architecture that allows you to subscribe to framework events:
|
|
206
|
-
|
|
207
|
-
```typescript
|
|
208
|
-
import { HookManagerEvent } from 'yinzerflow/constants/hooks';
|
|
209
|
-
|
|
210
|
-
// Subscribe to hook execution events
|
|
211
|
-
app.hooks.on(HookManagerEvent.BEFORE_ALL_EXECUTED, (context, hook) => {
|
|
212
|
-
console.log(`Executed beforeAll hook for ${context.request.path}`);
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
// Subscribe to hook addition events
|
|
216
|
-
app.hooks.on(HookManagerEvent.HOOK_ADDED, (hook) => {
|
|
217
|
-
console.log(`Added new hook for paths: ${JSON.stringify(hook.paths)}`);
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
// Subscribe to route registration events
|
|
221
|
-
app.routes.on('route-registered', (route) => {
|
|
222
|
-
console.log(`Registered route: ${route.method} ${route.path}`);
|
|
223
|
-
});
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
### Connection Management
|
|
227
|
-
|
|
228
|
-
YinzerFlow includes built-in connection management that handles tracking, monitoring, and graceful shutdown of server connections. This is managed internally by the framework to ensure reliable operation.
|
|
229
|
-
|
|
230
|
-
#### Configuration
|
|
231
|
-
|
|
232
|
-
You can configure connection management options when creating a YinzerFlow instance:
|
|
233
|
-
|
|
234
|
-
```typescript
|
|
235
|
-
import { YinzerFlow } from 'yinzerflow';
|
|
236
|
-
|
|
237
|
-
// Create a YinzerFlow instance with connection management options
|
|
238
|
-
const app = new YinzerFlow({
|
|
239
|
-
port: 3000,
|
|
240
|
-
connectionOptions: {
|
|
241
|
-
// Maximum time (in ms) to wait for connections to close during shutdown
|
|
242
|
-
gracefulShutdownTimeout: 10000,
|
|
243
|
-
// Socket timeout in milliseconds (how long until inactive connections are closed)
|
|
244
|
-
socketTimeout: 60000
|
|
245
|
-
}
|
|
246
|
-
});
|
|
247
|
-
```
|
|
248
|
-
|
|
249
|
-
#### Graceful Shutdown
|
|
250
|
-
|
|
251
|
-
To implement graceful shutdown in your application:
|
|
252
|
-
|
|
253
|
-
```typescript
|
|
254
|
-
// Graceful shutdown example
|
|
255
|
-
process.on('SIGTERM', async () => {
|
|
256
|
-
console.log('SIGTERM received, shutting down gracefully');
|
|
257
|
-
|
|
258
|
-
// This will:
|
|
259
|
-
// 1. Stop accepting new connections
|
|
260
|
-
// 2. Wait for existing connections to complete (up to the configured timeout)
|
|
261
|
-
// 3. Close the server
|
|
262
|
-
await app.close();
|
|
263
|
-
|
|
264
|
-
console.log('Server shut down gracefully');
|
|
265
|
-
process.exit(0);
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
app.listen();
|
|
269
|
-
console.log('Server running on http://localhost:3000');
|
|
270
|
-
```
|
|
271
|
-
|
|
272
|
-
The `close()` method handles the entire shutdown process, including:
|
|
273
|
-
- Stopping the server from accepting new connections
|
|
274
|
-
- Waiting for existing connections to finish (respecting the configured timeout)
|
|
275
|
-
- Closing all remaining connections and the server itself
|
|
276
|
-
|
|
277
|
-
This ensures that your application can shut down cleanly without abruptly terminating active connections.
|
|
278
|
-
|
|
279
|
-
### Path Matching Patterns
|
|
280
|
-
|
|
281
|
-
YinzerFlow supports advanced path matching patterns for hooks:
|
|
282
|
-
|
|
283
|
-
```typescript
|
|
284
|
-
// Exact path matching
|
|
285
|
-
app.beforeAll(adminHook, { paths: ['/admin'] });
|
|
286
|
-
|
|
287
|
-
// Wildcard matching
|
|
288
|
-
app.beforeAll(apiHook, { paths: ['/api/*'] });
|
|
289
|
-
|
|
290
|
-
// Parameter matching
|
|
291
|
-
app.beforeAll(userHook, { paths: ['/users/:id'] });
|
|
292
|
-
|
|
293
|
-
// Regular expression matching
|
|
294
|
-
app.beforeAll(secureHook, { paths: [/^\/secure\/.+/] });
|
|
295
|
-
```
|
|
296
|
-
|
|
297
|
-
## Contributing to Documentation
|
|
298
|
-
|
|
299
|
-
We welcome contributions to improve this documentation! If you find any issues or have suggestions for improvements, please feel free to submit a pull request.
|
|
300
|
-
|
|
301
|
-
When contributing to documentation:
|
|
302
|
-
|
|
303
|
-
1. Use clear, concise language
|
|
304
|
-
2. Include code examples where appropriate
|
|
305
|
-
3. Follow Markdown best practices
|
|
306
|
-
4. Test all links to ensure they work correctly
|
|
307
|
-
|
|
308
|
-
## Documentation Structure
|
|
309
|
-
|
|
310
|
-
The documentation is organized as follows:
|
|
311
|
-
|
|
312
|
-
```
|
|
313
|
-
docs/
|
|
314
|
-
├── README.md # This file - overview and getting started
|
|
315
|
-
├── routing.md # Comprehensive guide to the routing system
|
|
316
|
-
├── hooks.md # In-depth documentation of the hooks system
|
|
317
|
-
├── content-types.md # Working with different content types
|
|
318
|
-
├── error-handling.md # Guide to handling errors at different levels in your application
|
|
319
|
-
```
|
|
320
|
-
|
|
321
|
-
Additional documentation files will be added as the framework evolves, including:
|
|
322
|
-
|
|
323
|
-
- Error handling and logging
|
|
324
|
-
- Performance optimization guides
|
|
325
|
-
- Security best practices
|
|
326
|
-
- Deployment strategies
|
|
327
|
-
- Troubleshooting and FAQs
|
package/docs/content-types.md
DELETED
|
@@ -1,390 +0,0 @@
|
|
|
1
|
-
# Content Type Handling
|
|
2
|
-
|
|
3
|
-
YinzerFlow provides robust support for parsing and handling various content types in HTTP requests and responses. This document explains the core concepts, features, and best practices for working with different content types.
|
|
4
|
-
|
|
5
|
-
## Core Concepts
|
|
6
|
-
|
|
7
|
-
In web applications, HTTP requests and responses can contain data in various formats, such as JSON, XML, form data, and more. YinzerFlow automatically detects and parses these formats based on the `Content-Type` header, making it easy to work with different types of data.
|
|
8
|
-
|
|
9
|
-
### Type Safety
|
|
10
|
-
|
|
11
|
-
YinzerFlow provides TypeScript interfaces and type guards for each supported content type, allowing you to work with request bodies in a type-safe manner.
|
|
12
|
-
|
|
13
|
-
## Supported Content Types
|
|
14
|
-
|
|
15
|
-
YinzerFlow supports the following content types out of the box:
|
|
16
|
-
|
|
17
|
-
| Content Type | MIME Type | Type Interface | Type Guard | Description |
|
|
18
|
-
|--------------|-----------|----------------|------------|-------------|
|
|
19
|
-
| JSON | `application/json` | `TJsonData` | `isJsonData` | JSON data as a generic object |
|
|
20
|
-
| XML (Coming Soon) | `application/xml` | `TXmlData` | `isXmlData` | XML data with elements, attributes, and children |
|
|
21
|
-
| Multipart Form | `multipart/form-data` | `IMultipartFormData` | `isMultipartFormData` | Form data with fields and file uploads ([See Supported File Parsers](./file-parsers.md)) |
|
|
22
|
-
|
|
23
|
-
## Working with Content Types
|
|
24
|
-
|
|
25
|
-
There are two main approaches to working with parsed request bodies in YinzerFlow:
|
|
26
|
-
|
|
27
|
-
### Approach 1: Type Casting (Simple but Less Safe)
|
|
28
|
-
|
|
29
|
-
This approach uses TypeScript's type assertion to specify the expected content type:
|
|
30
|
-
|
|
31
|
-
```typescript
|
|
32
|
-
import { IMultipartFormData } from 'yinzerflow/types/http/Request';
|
|
33
|
-
|
|
34
|
-
app.post('/upload', ({ request }) => {
|
|
35
|
-
// Use type assertion - simple but no runtime validation
|
|
36
|
-
const body = request.body as IMultipartFormData;
|
|
37
|
-
|
|
38
|
-
// Access fields and files directly
|
|
39
|
-
const { fields, files } = body;
|
|
40
|
-
|
|
41
|
-
// Process the data...
|
|
42
|
-
return { success: true };
|
|
43
|
-
});
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
**When to use this approach:**
|
|
47
|
-
- When you're certain about the content type (e.g., an internal API with controlled clients)
|
|
48
|
-
- When performance is critical and you want to avoid runtime type checking
|
|
49
|
-
- In simple applications where type safety is less important
|
|
50
|
-
|
|
51
|
-
### Approach 2: Type Guards (Safer and Recommended)
|
|
52
|
-
|
|
53
|
-
This approach uses runtime type checking for better safety:
|
|
54
|
-
|
|
55
|
-
```typescript
|
|
56
|
-
import { isMultipartFormData } from 'yinzerflow/types/http/Request';
|
|
57
|
-
|
|
58
|
-
app.post('/upload', ({ request, response }) => {
|
|
59
|
-
// Runtime type checking
|
|
60
|
-
if (!isMultipartFormData(request.body)) {
|
|
61
|
-
response.setStatus(400);
|
|
62
|
-
return { error: 'Expected multipart form data' };
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// TypeScript knows request.body is IMultipartFormData here
|
|
66
|
-
const { fields, files } = request.body;
|
|
67
|
-
|
|
68
|
-
// Process the data...
|
|
69
|
-
return { success: true };
|
|
70
|
-
});
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
**When to use this approach:**
|
|
74
|
-
- When handling requests from external clients
|
|
75
|
-
- When an endpoint might receive different content types
|
|
76
|
-
- When you want to provide clear error messages for invalid requests
|
|
77
|
-
- In most production applications (recommended approach)
|
|
78
|
-
|
|
79
|
-
For more details on handling errors, including content type validation errors, refer to the [Error Handling](./error-handling.md) documentation.
|
|
80
|
-
|
|
81
|
-
## Detailed Content Type Examples
|
|
82
|
-
|
|
83
|
-
### JSON Data
|
|
84
|
-
|
|
85
|
-
JSON is one of the most common formats for API requests and responses:
|
|
86
|
-
|
|
87
|
-
```typescript
|
|
88
|
-
import { isJsonData } from 'yinzerflow';
|
|
89
|
-
|
|
90
|
-
app.post('/api/users', ({ request, response }) => {
|
|
91
|
-
if (!isJsonData<{ name: string; email: string; age?: number }>(request.body)) {
|
|
92
|
-
response.setStatus(400);
|
|
93
|
-
return { error: 'Expected JSON data' };
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Validate required fields
|
|
97
|
-
const { name, email, age } = request.body;
|
|
98
|
-
if (!name || !email) {
|
|
99
|
-
response.setStatus(400);
|
|
100
|
-
return { error: 'Name and email are required' };
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Process the data
|
|
104
|
-
const newUser = {
|
|
105
|
-
id: generateId(),
|
|
106
|
-
name,
|
|
107
|
-
email,
|
|
108
|
-
age: age || null,
|
|
109
|
-
createdAt: new Date().toISOString()
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
// Save the user (example)
|
|
113
|
-
saveUser(newUser);
|
|
114
|
-
|
|
115
|
-
response.setStatus(201);
|
|
116
|
-
return newUser;
|
|
117
|
-
});
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
#### Type Definition
|
|
121
|
-
|
|
122
|
-
```typescript
|
|
123
|
-
// The JSON data type
|
|
124
|
-
type TJsonData<T = unknown> = Record<string, unknown> & T;
|
|
125
|
-
|
|
126
|
-
// Type guard for JSON data
|
|
127
|
-
const isJsonData = <T = unknown>(body: TRequestBody): body is TJsonData<T> => !isMultipartFormData(body) && typeof body === 'object';
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
### Multipart Form Data (File Uploads)
|
|
131
|
-
|
|
132
|
-
Multipart form data is used for file uploads and complex forms:
|
|
133
|
-
|
|
134
|
-
```typescript
|
|
135
|
-
import { isMultipartFormData } from 'yinzerflow';
|
|
136
|
-
import { saveFile } from './file-service';
|
|
137
|
-
|
|
138
|
-
app.post('/api/upload', ({ request, response }) => {
|
|
139
|
-
if (!isMultipartFormData(request.body)) {
|
|
140
|
-
response.setStatus(415);
|
|
141
|
-
return { error: 'Expected multipart form data' };
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const { fields, files } = request.body;
|
|
145
|
-
|
|
146
|
-
// Validate form fields
|
|
147
|
-
const userId = fields.userId;
|
|
148
|
-
if (!userId) {
|
|
149
|
-
response.setStatus(400);
|
|
150
|
-
return { error: 'User ID is required' };
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// Process uploaded files
|
|
154
|
-
const uploadedFiles = [];
|
|
155
|
-
for (const file of files) {
|
|
156
|
-
// Check file type
|
|
157
|
-
if (!file.contentType.startsWith('image/')) {
|
|
158
|
-
response.setStatus(400);
|
|
159
|
-
return { error: `File ${file.name} is not an image` };
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Check file size (max 5MB)
|
|
163
|
-
if (file.size > 5 * 1024 * 1024) {
|
|
164
|
-
response.setStatus(400);
|
|
165
|
-
return { error: `File ${file.name} exceeds the 5MB limit` };
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Save the file (example)
|
|
169
|
-
const savedPath = saveFile(file.path, userId);
|
|
170
|
-
|
|
171
|
-
uploadedFiles.push({
|
|
172
|
-
originalName: file.name,
|
|
173
|
-
size: file.size,
|
|
174
|
-
type: file.contentType,
|
|
175
|
-
savedPath
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
response.setStatus(201);
|
|
180
|
-
return {
|
|
181
|
-
success: true,
|
|
182
|
-
message: `Uploaded ${uploadedFiles.length} files for user ${userId}`,
|
|
183
|
-
files: uploadedFiles
|
|
184
|
-
};
|
|
185
|
-
});
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
#### Type Definition
|
|
189
|
-
|
|
190
|
-
```typescript
|
|
191
|
-
// The multipart form data interface
|
|
192
|
-
interface IMultipartFormData {
|
|
193
|
-
fields: Record<string, string>;
|
|
194
|
-
files: Record<string, UploadedFile>;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// The uploaded file interface
|
|
198
|
-
interface UploadedFile {
|
|
199
|
-
/** Original filename provided by the client */
|
|
200
|
-
filename: string;
|
|
201
|
-
/** MIME type of the file */
|
|
202
|
-
contentType: string;
|
|
203
|
-
/** Size of the file in bytes */
|
|
204
|
-
size: number;
|
|
205
|
-
/** file content */
|
|
206
|
-
content: TYamlData | string;
|
|
207
|
-
/** Additional metadata about the file */
|
|
208
|
-
metadata?: Record<string, string>;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// Type guard for JSON data
|
|
212
|
-
const isMultipartFormData = (body: TRequestBody): body is IMultipartFormData =>
|
|
213
|
-
body !== null && typeof body === 'object' && 'fields' in body && 'files' in body && Array.isArray(body.files) && typeof (<any>body.fields) === 'object';
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
### XML Data (Coming Soon)
|
|
217
|
-
|
|
218
|
-
XML is commonly used in enterprise systems and SOAP APIs:
|
|
219
|
-
|
|
220
|
-
## Best Practices
|
|
221
|
-
|
|
222
|
-
### 1. Always Validate Request Bodies
|
|
223
|
-
|
|
224
|
-
Always validate the content type and structure of request bodies, especially for public APIs:
|
|
225
|
-
|
|
226
|
-
```typescript
|
|
227
|
-
app.post('/api/data', ({ request, response }) => {
|
|
228
|
-
// Validate content type
|
|
229
|
-
if (!isJsonData(request.body)) {
|
|
230
|
-
response.setStatus(400);
|
|
231
|
-
return { error: 'Expected JSON data' };
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// Validate required fields
|
|
235
|
-
const { name, email } = request.body;
|
|
236
|
-
if (!name || !email) {
|
|
237
|
-
response.setStatus(400);
|
|
238
|
-
return { error: 'Name and email are required' };
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// Validate email format
|
|
242
|
-
if (!isValidEmail(email)) {
|
|
243
|
-
response.setStatus(400);
|
|
244
|
-
return { error: 'Invalid email format' };
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// Process the data...
|
|
248
|
-
});
|
|
249
|
-
```
|
|
250
|
-
|
|
251
|
-
### 2. Use Type Guards for Public APIs
|
|
252
|
-
|
|
253
|
-
For public APIs, always use type guards to validate request bodies:
|
|
254
|
-
|
|
255
|
-
```typescript
|
|
256
|
-
// Good practice for public APIs
|
|
257
|
-
if (!isJsonData(request.body)) {
|
|
258
|
-
response.setStatus(400);
|
|
259
|
-
return { error: 'Expected JSON data' };
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// Avoid type assertions for public APIs
|
|
263
|
-
const body = request.body as TJsonData; // Unsafe
|
|
264
|
-
```
|
|
265
|
-
|
|
266
|
-
### 3. Set Appropriate Content-Type Headers for Responses
|
|
267
|
-
|
|
268
|
-
Set the appropriate `Content-Type` header for responses:
|
|
269
|
-
|
|
270
|
-
```typescript
|
|
271
|
-
app.get('/api/data.csv', ({ response }) => {
|
|
272
|
-
const csvContent = generateCsvData();
|
|
273
|
-
|
|
274
|
-
response.setStatus(200);
|
|
275
|
-
response.modifyHeader('Content-Type', 'text/csv');
|
|
276
|
-
response.modifyHeader('Content-Disposition', 'attachment; filename="data.csv"');
|
|
277
|
-
return csvContent;
|
|
278
|
-
});
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
### 4. Handle Large File Uploads Properly
|
|
282
|
-
|
|
283
|
-
For file uploads, implement proper validation and limits:
|
|
284
|
-
|
|
285
|
-
```typescript
|
|
286
|
-
app.post('/api/upload', ({ request, response }) => {
|
|
287
|
-
if (!isMultipartFormData(request.body)) {
|
|
288
|
-
response.setStatus(400);
|
|
289
|
-
return { error: 'Expected multipart form data' };
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
const { files } = request.body;
|
|
293
|
-
|
|
294
|
-
// Check file count
|
|
295
|
-
if (Object.keys(files).length > 10) {
|
|
296
|
-
response.setStatus(400);
|
|
297
|
-
return { error: 'Maximum 10 files allowed' };
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
// Check file sizes
|
|
301
|
-
for (const [, file] of Object.entries(files)) {
|
|
302
|
-
if (file.size > 10 * 1024 * 1024) { // 10MB
|
|
303
|
-
response.setStatus(400);
|
|
304
|
-
return { error: `File ${file.name} exceeds the 10MB limit` };
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// Process files...
|
|
309
|
-
});
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
### 5. Use Structured Error Responses
|
|
313
|
-
|
|
314
|
-
Provide clear and structured error responses:
|
|
315
|
-
|
|
316
|
-
```typescript
|
|
317
|
-
app.post('/api/users', ({ request, response }) => {
|
|
318
|
-
if (!isJsonData(request.body)) {
|
|
319
|
-
response.setStatus(400);
|
|
320
|
-
return {
|
|
321
|
-
success: false,
|
|
322
|
-
error: 'INVALID_CONTENT_TYPE',
|
|
323
|
-
message: 'Expected JSON data'
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
// Validate fields...
|
|
328
|
-
const errors = validateUserData(request.body);
|
|
329
|
-
if (errors.length > 0) {
|
|
330
|
-
response.setStatus(400);
|
|
331
|
-
return {
|
|
332
|
-
success: false,
|
|
333
|
-
error: 'VALIDATION_ERROR',
|
|
334
|
-
message: 'Invalid user data',
|
|
335
|
-
details: errors
|
|
336
|
-
};
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
// Process the data...
|
|
340
|
-
});
|
|
341
|
-
```
|
|
342
|
-
|
|
343
|
-
## Advanced Topics
|
|
344
|
-
|
|
345
|
-
### Custom Content Type Parsers (Future Idea)
|
|
346
|
-
|
|
347
|
-
YinzerFlow allows you to register custom content type parsers:
|
|
348
|
-
|
|
349
|
-
### Content Negotiation
|
|
350
|
-
|
|
351
|
-
Implement content negotiation to support multiple response formats:
|
|
352
|
-
|
|
353
|
-
```typescript
|
|
354
|
-
app.get('/api/users/:id', ({ request, response }) => {
|
|
355
|
-
const user = getUserById(request.params.id);
|
|
356
|
-
if (!user) {
|
|
357
|
-
response.setStatus(404);
|
|
358
|
-
return { error: 'User not found' };
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
// Check Accept header
|
|
362
|
-
const acceptHeader = request.headers.accept || 'application/json';
|
|
363
|
-
|
|
364
|
-
if (acceptHeader.includes('application/xml')) {
|
|
365
|
-
// Return XML response
|
|
366
|
-
const xmlData = convertToXml({ user });
|
|
367
|
-
response.modifyHeader('Content-Type', 'application/xml');
|
|
368
|
-
return xmlData;
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
if (acceptHeader.includes('text/csv')) {
|
|
372
|
-
// Return CSV response
|
|
373
|
-
const csvData = convertToCsv([user]);
|
|
374
|
-
response.modifyHeader('Content-Type', 'text/csv');
|
|
375
|
-
return csvData;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
// Default to JSON
|
|
379
|
-
return user;
|
|
380
|
-
});
|
|
381
|
-
```
|
|
382
|
-
|
|
383
|
-
### Handling Binary Data
|
|
384
|
-
|
|
385
|
-
For binary data like images or PDFs we only support uploads using from-data at this time
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
## Conclusion
|
|
389
|
-
|
|
390
|
-
YinzerFlow's content type handling system provides a flexible and type-safe way to work with various data formats in your web applications. By following the best practices outlined in this document, you can build robust APIs that handle different content types correctly and provide a great developer experience.
|