bxo 0.0.5-dev.8 → 0.0.5-dev.80
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 +258 -483
- package/example/cookie-example.ts +151 -0
- package/example/cors-example.ts +49 -0
- package/example/index.html +5 -0
- package/example/index.ts +191 -0
- package/example/multipart-example.ts +203 -0
- package/example/openapi-example.ts +132 -0
- package/example/passthrough-validation-example.ts +115 -0
- package/example/url-encoding-example.ts +93 -0
- package/example/websocket-example.ts +132 -0
- package/package.json +8 -8
- package/plugins/cors.ts +123 -73
- package/plugins/index.ts +2 -11
- package/plugins/openapi.ts +204 -0
- package/src/index.ts +960 -0
- package/test-url-encoding.ts +20 -0
- package/tsconfig.json +3 -5
- package/.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc +0 -111
- package/example.ts +0 -183
- package/index.ts +0 -835
- package/plugins/auth.ts +0 -119
- package/plugins/logger.ts +0 -109
- package/plugins/ratelimit.ts +0 -140
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import BXO, { z } from "../src";
|
|
2
|
+
|
|
3
|
+
async function main() {
|
|
4
|
+
const app = new BXO({ serve: { port: 3001 } });
|
|
5
|
+
|
|
6
|
+
// Example 1: Simple cookie setting
|
|
7
|
+
app.get("/set-simple-cookie", (ctx) => {
|
|
8
|
+
// Set a simple cookie
|
|
9
|
+
ctx.set.cookie("theme", "dark");
|
|
10
|
+
|
|
11
|
+
return ctx.json({ message: "Simple cookie set!" });
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
// Example 2: Cookie with options
|
|
15
|
+
app.get("/set-secure-cookie", (ctx) => {
|
|
16
|
+
// Set a secure cookie with various options
|
|
17
|
+
ctx.set.cookie("sessionId", "abc123", {
|
|
18
|
+
httpOnly: true,
|
|
19
|
+
secure: true,
|
|
20
|
+
sameSite: "strict",
|
|
21
|
+
maxAge: 3600, // 1 hour
|
|
22
|
+
path: "/"
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
return ctx.json({ message: "Secure cookie set!" });
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// Example 3: Multiple cookies
|
|
29
|
+
app.get("/set-multiple-cookies", (ctx) => {
|
|
30
|
+
// Set multiple cookies
|
|
31
|
+
ctx.set.cookie("user", "john_doe", {
|
|
32
|
+
httpOnly: true,
|
|
33
|
+
maxAge: 86400 // 1 day
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
ctx.set.cookie("preferences", "dark_mode", {
|
|
37
|
+
maxAge: 604800 // 1 week
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
ctx.set.cookie("lastVisit", new Date().toISOString(), {
|
|
41
|
+
expires: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000) // 30 days
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return ctx.json({ message: "Multiple cookies set!" });
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// Example 4: Reading cookies
|
|
48
|
+
app.get("/read-cookies", (ctx) => {
|
|
49
|
+
return ctx.json({
|
|
50
|
+
message: "Current cookies:",
|
|
51
|
+
cookies: ctx.cookies,
|
|
52
|
+
theme: ctx.cookies.theme || "not set",
|
|
53
|
+
sessionId: ctx.cookies.sessionId || "not set",
|
|
54
|
+
user: ctx.cookies.user || "not set"
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// Example 5: Login with cookie validation
|
|
59
|
+
app.post("/login", (ctx) => {
|
|
60
|
+
const { username, password } = ctx.body;
|
|
61
|
+
|
|
62
|
+
// Simple validation (in real app, check against database)
|
|
63
|
+
if (username === "admin" && password === "password") {
|
|
64
|
+
// Set session cookie
|
|
65
|
+
ctx.set.cookie("sessionId", `session_${Date.now()}`, {
|
|
66
|
+
httpOnly: true,
|
|
67
|
+
secure: process.env.NODE_ENV === "production",
|
|
68
|
+
sameSite: "strict",
|
|
69
|
+
maxAge: 3600 // 1 hour
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
ctx.set.cookie("username", username, {
|
|
73
|
+
maxAge: 3600
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
return ctx.json({
|
|
77
|
+
message: "Login successful!",
|
|
78
|
+
username
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return ctx.status(401, { error: "Invalid credentials" });
|
|
83
|
+
}, {
|
|
84
|
+
body: z.object({
|
|
85
|
+
username: z.string(),
|
|
86
|
+
password: z.string()
|
|
87
|
+
})
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Example 6: Protected route with cookie validation
|
|
91
|
+
app.get("/profile", (ctx) => {
|
|
92
|
+
const sessionId = ctx.cookies.sessionId;
|
|
93
|
+
const username = ctx.cookies.username;
|
|
94
|
+
|
|
95
|
+
if (!sessionId) {
|
|
96
|
+
return ctx.status(401, { error: "Not authenticated" });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return ctx.json({
|
|
100
|
+
message: "Welcome to your profile!",
|
|
101
|
+
username,
|
|
102
|
+
sessionId: sessionId.substring(0, 10) + "..." // Don't expose full session ID
|
|
103
|
+
});
|
|
104
|
+
}, {
|
|
105
|
+
cookies: z.object({
|
|
106
|
+
sessionId: z.string().optional(),
|
|
107
|
+
username: z.string().optional()
|
|
108
|
+
})
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Example 7: Logout (clear cookies)
|
|
112
|
+
app.post("/logout", (ctx) => {
|
|
113
|
+
// Clear cookies by setting them with maxAge: 0
|
|
114
|
+
ctx.set.cookie("sessionId", "", {
|
|
115
|
+
maxAge: 0,
|
|
116
|
+
path: "/"
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
ctx.set.cookie("username", "", {
|
|
120
|
+
maxAge: 0,
|
|
121
|
+
path: "/"
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
return ctx.json({ message: "Logged out successfully" });
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// Example 8: Cookie with domain and path
|
|
128
|
+
app.get("/set-domain-cookie", (ctx) => {
|
|
129
|
+
ctx.set.cookie("globalPref", "enabled", {
|
|
130
|
+
domain: "localhost", // or your domain
|
|
131
|
+
path: "/api",
|
|
132
|
+
maxAge: 86400
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
return ctx.json({ message: "Domain-specific cookie set!" });
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
app.start();
|
|
139
|
+
console.log(`Cookie example server running on http://localhost:${app.server?.port}`);
|
|
140
|
+
console.log("\nTry these endpoints:");
|
|
141
|
+
console.log("GET /set-simple-cookie");
|
|
142
|
+
console.log("GET /set-secure-cookie");
|
|
143
|
+
console.log("GET /set-multiple-cookies");
|
|
144
|
+
console.log("GET /read-cookies");
|
|
145
|
+
console.log("POST /login (with body: {\"username\": \"admin\", \"password\": \"password\"})");
|
|
146
|
+
console.log("GET /profile");
|
|
147
|
+
console.log("POST /logout");
|
|
148
|
+
console.log("GET /set-domain-cookie");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import BXO from "../src/index";
|
|
2
|
+
import { cors } from "../plugins";
|
|
3
|
+
|
|
4
|
+
const app = new BXO();
|
|
5
|
+
|
|
6
|
+
// Use the CORS plugin
|
|
7
|
+
app.use(cors({
|
|
8
|
+
origin: ["http://localhost:3000", "http://localhost:3001"],
|
|
9
|
+
methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
|
|
10
|
+
allowedHeaders: ["Content-Type", "Authorization"],
|
|
11
|
+
credentials: true
|
|
12
|
+
}));
|
|
13
|
+
|
|
14
|
+
// Add some routes
|
|
15
|
+
app.get("/api/users", async (ctx) => {
|
|
16
|
+
return ctx.json([
|
|
17
|
+
{ id: 1, name: "John Doe" },
|
|
18
|
+
{ id: 2, name: "Jane Smith" }
|
|
19
|
+
]);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
app.post("/api/users", async (ctx) => {
|
|
23
|
+
const user = ctx.body as { name: string };
|
|
24
|
+
return ctx.json({ id: 3, name: user.name }, 201);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Custom beforeRequest hook example
|
|
28
|
+
app.beforeRequest(async (req) => {
|
|
29
|
+
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
|
|
30
|
+
return req; // Continue with the request
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// Custom afterRequest hook example
|
|
34
|
+
app.afterRequest(async (req, res) => {
|
|
35
|
+
console.log(`[${new Date().toISOString()}] Response: ${res.status}`);
|
|
36
|
+
return res; // Return the modified response
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Custom error handler
|
|
40
|
+
app.onError(async (error, req) => {
|
|
41
|
+
console.error(`Error handling ${req.method} ${req.url}:`, error);
|
|
42
|
+
return new Response("Something went wrong", { status: 500 });
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Start the server
|
|
46
|
+
app.start();
|
|
47
|
+
|
|
48
|
+
console.log("Server running on http://localhost:3000");
|
|
49
|
+
console.log("Try making a CORS request from another origin!");
|
package/example/index.ts
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import BXO, { z } from "../src";
|
|
2
|
+
import index from "./index.html";
|
|
3
|
+
import { openapi } from "../plugins/openapi";
|
|
4
|
+
|
|
5
|
+
async function main() {
|
|
6
|
+
const bxo = new BXO({ serve: { port: 0 } });
|
|
7
|
+
|
|
8
|
+
bxo.default("/", index);
|
|
9
|
+
bxo.default("/*", index);
|
|
10
|
+
|
|
11
|
+
// API routes with comprehensive metadata
|
|
12
|
+
bxo.get("/api/get/:id", (ctx) => {
|
|
13
|
+
return new Response(ctx.params.id + ctx.query.name, {
|
|
14
|
+
headers: {
|
|
15
|
+
"Content-Type": "text/html"
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
}, {
|
|
19
|
+
query: z.object({
|
|
20
|
+
name: z.number()
|
|
21
|
+
}),
|
|
22
|
+
response: {
|
|
23
|
+
200: z.object({
|
|
24
|
+
name: z.string()
|
|
25
|
+
})
|
|
26
|
+
},
|
|
27
|
+
detail: {
|
|
28
|
+
tags: ["API"],
|
|
29
|
+
summary: "Get data by ID",
|
|
30
|
+
description: "Retrieve data using an ID and name query parameter",
|
|
31
|
+
params: {
|
|
32
|
+
id: z.string().describe("Unique identifier for the data")
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
bxo.post("/api/post", (ctx) => {
|
|
38
|
+
console.log(ctx.body)
|
|
39
|
+
return new Response("Hello" + ctx.body.name, {
|
|
40
|
+
headers: {
|
|
41
|
+
"Content-Type": "text/html"
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}, {
|
|
45
|
+
detail: {
|
|
46
|
+
tags: ["API"],
|
|
47
|
+
summary: "Create new data",
|
|
48
|
+
description: "Submit new data with name and avatar file",
|
|
49
|
+
defaultContentType: "multipart/form-data"
|
|
50
|
+
},
|
|
51
|
+
body: z.object({
|
|
52
|
+
name: z.string(),
|
|
53
|
+
avatar: z.file()
|
|
54
|
+
}),
|
|
55
|
+
response: {
|
|
56
|
+
200: z.object({
|
|
57
|
+
name: z.string()
|
|
58
|
+
}),
|
|
59
|
+
400: z.object({
|
|
60
|
+
error: z.string()
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Additional routes to showcase different features
|
|
66
|
+
bxo.get("/api/users", (ctx) => {
|
|
67
|
+
return ctx.json({ users: ["John", "Jane", "Bob"] });
|
|
68
|
+
}, {
|
|
69
|
+
detail: {
|
|
70
|
+
tags: ["Users"],
|
|
71
|
+
summary: "Get all users",
|
|
72
|
+
description: "Retrieve a list of all users in the system"
|
|
73
|
+
},
|
|
74
|
+
response: {
|
|
75
|
+
200: z.object({
|
|
76
|
+
users: z.array(z.string())
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
bxo.get("/api/users/:id", (ctx) => {
|
|
82
|
+
const id = ctx.params.id;
|
|
83
|
+
return ctx.json({ user: { id, name: "John Doe", email: "john@example.com" } });
|
|
84
|
+
}, {
|
|
85
|
+
detail: {
|
|
86
|
+
tags: ["Users"],
|
|
87
|
+
summary: "Get user by ID",
|
|
88
|
+
description: "Retrieve a specific user by their unique identifier",
|
|
89
|
+
params: {
|
|
90
|
+
id: z.string().describe("User's unique identifier")
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
response: {
|
|
94
|
+
200: z.object({
|
|
95
|
+
user: z.object({
|
|
96
|
+
id: z.string(),
|
|
97
|
+
name: z.string(),
|
|
98
|
+
email: z.string()
|
|
99
|
+
})
|
|
100
|
+
}),
|
|
101
|
+
404: z.object({
|
|
102
|
+
error: z.string()
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
bxo.post("/api/users", (ctx) => {
|
|
108
|
+
const userData = ctx.body;
|
|
109
|
+
return ctx.json({ message: "User created", user: userData });
|
|
110
|
+
}, {
|
|
111
|
+
detail: {
|
|
112
|
+
tags: ["Users"],
|
|
113
|
+
summary: "Create new user",
|
|
114
|
+
description: "Create a new user account with the provided information"
|
|
115
|
+
},
|
|
116
|
+
body: z.object({
|
|
117
|
+
name: z.string().min(1, "Name is required"),
|
|
118
|
+
email: z.string().email("Invalid email format"),
|
|
119
|
+
age: z.number().min(18, "Must be at least 18 years old").optional()
|
|
120
|
+
}),
|
|
121
|
+
response: {
|
|
122
|
+
201: z.object({
|
|
123
|
+
message: z.string(),
|
|
124
|
+
user: z.object({
|
|
125
|
+
name: z.string(),
|
|
126
|
+
email: z.string(),
|
|
127
|
+
age: z.number().optional()
|
|
128
|
+
})
|
|
129
|
+
}),
|
|
130
|
+
400: z.object({
|
|
131
|
+
error: z.string(),
|
|
132
|
+
issues: z.array(z.any()).optional()
|
|
133
|
+
})
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Health check route
|
|
138
|
+
bxo.get("/health", (ctx) => {
|
|
139
|
+
return ctx.json({
|
|
140
|
+
status: "ok",
|
|
141
|
+
timestamp: new Date().toISOString(),
|
|
142
|
+
uptime: process.uptime()
|
|
143
|
+
});
|
|
144
|
+
}, {
|
|
145
|
+
detail: {
|
|
146
|
+
tags: ["System"],
|
|
147
|
+
summary: "Health check",
|
|
148
|
+
description: "Check the health status of the API server"
|
|
149
|
+
},
|
|
150
|
+
response: {
|
|
151
|
+
200: z.object({
|
|
152
|
+
status: z.string(),
|
|
153
|
+
timestamp: z.string(),
|
|
154
|
+
uptime: z.number()
|
|
155
|
+
})
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Use OpenAPI plugin with enhanced configuration
|
|
160
|
+
bxo.use(openapi({
|
|
161
|
+
path: "/docs",
|
|
162
|
+
jsonPath: "/openapi.json",
|
|
163
|
+
defaultTags: ["API"],
|
|
164
|
+
securitySchemes: {
|
|
165
|
+
bearerAuth: {
|
|
166
|
+
type: "http",
|
|
167
|
+
scheme: "bearer",
|
|
168
|
+
bearerFormat: "JWT",
|
|
169
|
+
description: "JWT token for authentication"
|
|
170
|
+
},
|
|
171
|
+
apiKeyAuth: {
|
|
172
|
+
type: "apiKey",
|
|
173
|
+
in: "header",
|
|
174
|
+
name: "X-API-Key",
|
|
175
|
+
description: "API key for authentication"
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
globalSecurity: [
|
|
179
|
+
{ bearerAuth: [] },
|
|
180
|
+
{ apiKeyAuth: [] }
|
|
181
|
+
],
|
|
182
|
+
openapiConfig: {}
|
|
183
|
+
}));
|
|
184
|
+
|
|
185
|
+
bxo.start();
|
|
186
|
+
console.log(`Server is running on http://localhost:${bxo.server?.port}`);
|
|
187
|
+
console.log(`OpenAPI documentation available at http://localhost:${bxo.server?.port}/docs`);
|
|
188
|
+
console.log(`OpenAPI JSON available at http://localhost:${bxo.server?.port}/openapi.json`);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
main();
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import BXO from "../src/index";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
|
|
4
|
+
const app = new BXO();
|
|
5
|
+
|
|
6
|
+
// Define Zod schema for the form data structure
|
|
7
|
+
const UserFormSchema = z.object({
|
|
8
|
+
name: z.string(),
|
|
9
|
+
email: z.string().email(),
|
|
10
|
+
password: z.string().min(6),
|
|
11
|
+
is_active: z.string().transform(val => val === "1"),
|
|
12
|
+
profile: z.object({
|
|
13
|
+
name: z.string()
|
|
14
|
+
}),
|
|
15
|
+
id: z.string().optional(),
|
|
16
|
+
created_at: z.string().optional(),
|
|
17
|
+
updated_at: z.string().optional(),
|
|
18
|
+
idx: z.string().transform(val => parseInt(val, 10))
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// Route to handle multipart/form-data with nested objects
|
|
22
|
+
app.post("/users", async (ctx) => {
|
|
23
|
+
// The form data is automatically parsed into nested objects
|
|
24
|
+
// ctx.body will contain the structured data based on the schema
|
|
25
|
+
console.log("Parsed form data:", ctx.body);
|
|
26
|
+
|
|
27
|
+
return ctx.json({
|
|
28
|
+
message: "User created successfully",
|
|
29
|
+
data: ctx.body
|
|
30
|
+
});
|
|
31
|
+
}, {
|
|
32
|
+
body: UserFormSchema,
|
|
33
|
+
detail: {
|
|
34
|
+
summary: "Create user with multipart/form-data",
|
|
35
|
+
description: "Handles form data with nested objects and arrays",
|
|
36
|
+
tags: ["Users"]
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Example with arrays
|
|
41
|
+
const ArrayFormSchema = z.object({
|
|
42
|
+
items: z.array(z.string()),
|
|
43
|
+
tags: z.array(z.string()),
|
|
44
|
+
profile: z.object({
|
|
45
|
+
name: z.string(),
|
|
46
|
+
age: z.string().transform(val => parseInt(val, 10))
|
|
47
|
+
})
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
app.post("/items", async (ctx) => {
|
|
51
|
+
console.log("Parsed array form data:", ctx.body);
|
|
52
|
+
|
|
53
|
+
return ctx.json({
|
|
54
|
+
message: "Items processed successfully",
|
|
55
|
+
data: ctx.body
|
|
56
|
+
});
|
|
57
|
+
}, {
|
|
58
|
+
body: ArrayFormSchema,
|
|
59
|
+
detail: {
|
|
60
|
+
summary: "Process items with arrays",
|
|
61
|
+
description: "Handles form data with arrays like items[0], items[1]",
|
|
62
|
+
tags: ["Items"]
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Test route to show how the parsing works
|
|
67
|
+
app.get("/test-parsing", async (ctx) => {
|
|
68
|
+
const html = `
|
|
69
|
+
<!DOCTYPE html>
|
|
70
|
+
<html>
|
|
71
|
+
<head>
|
|
72
|
+
<title>Multipart Form Data Test</title>
|
|
73
|
+
<style>
|
|
74
|
+
body { font-family: Arial, sans-serif; margin: 40px; }
|
|
75
|
+
.form-group { margin: 20px 0; }
|
|
76
|
+
label { display: block; margin-bottom: 5px; font-weight: bold; }
|
|
77
|
+
input, textarea { width: 300px; padding: 8px; margin-bottom: 10px; }
|
|
78
|
+
button { background: #007bff; color: white; padding: 10px 20px; border: none; cursor: pointer; }
|
|
79
|
+
.result { margin-top: 20px; padding: 20px; background: #f8f9fa; border-radius: 5px; }
|
|
80
|
+
</style>
|
|
81
|
+
</head>
|
|
82
|
+
<body>
|
|
83
|
+
<h1>Multipart Form Data Parsing Test</h1>
|
|
84
|
+
|
|
85
|
+
<h2>Test 1: Nested Objects (like your image example)</h2>
|
|
86
|
+
<form id="userForm" enctype="multipart/form-data">
|
|
87
|
+
<div class="form-group">
|
|
88
|
+
<label>Name:</label>
|
|
89
|
+
<input type="text" name="name" value="John Doe" required>
|
|
90
|
+
</div>
|
|
91
|
+
<div class="form-group">
|
|
92
|
+
<label>Email:</label>
|
|
93
|
+
<input type="email" name="email" value="john@example.com" required>
|
|
94
|
+
</div>
|
|
95
|
+
<div class="form-group">
|
|
96
|
+
<label>Password:</label>
|
|
97
|
+
<input type="password" name="password" value="password123" required>
|
|
98
|
+
</div>
|
|
99
|
+
<div class="form-group">
|
|
100
|
+
<label>Is Active:</label>
|
|
101
|
+
<input type="text" name="is_active" value="1">
|
|
102
|
+
</div>
|
|
103
|
+
<div class="form-group">
|
|
104
|
+
<label>Profile Name:</label>
|
|
105
|
+
<input type="text" name="profile[name]" value="John Profile" required>
|
|
106
|
+
</div>
|
|
107
|
+
<div class="form-group">
|
|
108
|
+
<label>ID:</label>
|
|
109
|
+
<input type="text" name="id" value="UUID()">
|
|
110
|
+
</div>
|
|
111
|
+
<div class="form-group">
|
|
112
|
+
<label>Created At:</label>
|
|
113
|
+
<input type="text" name="created_at" value="NOW()">
|
|
114
|
+
</div>
|
|
115
|
+
<div class="form-group">
|
|
116
|
+
<label>Updated At:</label>
|
|
117
|
+
<input type="text" name="updated_at" value="NOW()">
|
|
118
|
+
</div>
|
|
119
|
+
<div class="form-group">
|
|
120
|
+
<label>Index:</label>
|
|
121
|
+
<input type="text" name="idx" value="0">
|
|
122
|
+
</div>
|
|
123
|
+
<button type="submit">Submit User Form</button>
|
|
124
|
+
</form>
|
|
125
|
+
|
|
126
|
+
<h2>Test 2: Arrays</h2>
|
|
127
|
+
<form id="itemsForm" enctype="multipart/form-data">
|
|
128
|
+
<div class="form-group">
|
|
129
|
+
<label>Item 1:</label>
|
|
130
|
+
<input type="text" name="items[0]" value="Apple">
|
|
131
|
+
</div>
|
|
132
|
+
<div class="form-group">
|
|
133
|
+
<label>Item 2:</label>
|
|
134
|
+
<input type="text" name="items[1]" value="Banana">
|
|
135
|
+
</div>
|
|
136
|
+
<div class="form-group">
|
|
137
|
+
<label>Item 3:</label>
|
|
138
|
+
<input type="text" name="items[2]" value="Cherry">
|
|
139
|
+
</div>
|
|
140
|
+
<div class="form-group">
|
|
141
|
+
<label>Tag 1:</label>
|
|
142
|
+
<input type="text" name="tags[0]" value="fruit">
|
|
143
|
+
</div>
|
|
144
|
+
<div class="form-group">
|
|
145
|
+
<label>Tag 2:</label>
|
|
146
|
+
<input type="text" name="tags[1]" value="healthy">
|
|
147
|
+
</div>
|
|
148
|
+
<div class="form-group">
|
|
149
|
+
<label>Profile Name:</label>
|
|
150
|
+
<input type="text" name="profile[name]" value="Test Profile">
|
|
151
|
+
</div>
|
|
152
|
+
<div class="form-group">
|
|
153
|
+
<label>Profile Age:</label>
|
|
154
|
+
<input type="text" name="profile[age]" value="25">
|
|
155
|
+
</div>
|
|
156
|
+
<button type="submit">Submit Items Form</button>
|
|
157
|
+
</form>
|
|
158
|
+
|
|
159
|
+
<div id="result" class="result" style="display: none;">
|
|
160
|
+
<h3>Result:</h3>
|
|
161
|
+
<pre id="resultContent"></pre>
|
|
162
|
+
</div>
|
|
163
|
+
|
|
164
|
+
<script>
|
|
165
|
+
async function submitForm(form, endpoint) {
|
|
166
|
+
const formData = new FormData(form);
|
|
167
|
+
try {
|
|
168
|
+
const response = await fetch(endpoint, {
|
|
169
|
+
method: 'POST',
|
|
170
|
+
body: formData
|
|
171
|
+
});
|
|
172
|
+
const result = await response.json();
|
|
173
|
+
document.getElementById('result').style.display = 'block';
|
|
174
|
+
document.getElementById('resultContent').textContent = JSON.stringify(result, null, 2);
|
|
175
|
+
} catch (error) {
|
|
176
|
+
document.getElementById('result').style.display = 'block';
|
|
177
|
+
document.getElementById('resultContent').textContent = 'Error: ' + error.message;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
document.getElementById('userForm').addEventListener('submit', (e) => {
|
|
182
|
+
e.preventDefault();
|
|
183
|
+
submitForm(e.target, '/users');
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
document.getElementById('itemsForm').addEventListener('submit', (e) => {
|
|
187
|
+
e.preventDefault();
|
|
188
|
+
submitForm(e.target, '/items');
|
|
189
|
+
});
|
|
190
|
+
</script>
|
|
191
|
+
</body>
|
|
192
|
+
</html>
|
|
193
|
+
`;
|
|
194
|
+
|
|
195
|
+
return new Response(html, {
|
|
196
|
+
headers: { "Content-Type": "text/html" }
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
app.start();
|
|
201
|
+
|
|
202
|
+
console.log("🚀 Server running at http://localhost:3000");
|
|
203
|
+
console.log("📝 Test the multipart parsing at http://localhost:3000/test-parsing");
|