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
package/README.md
CHANGED
|
@@ -1,579 +1,354 @@
|
|
|
1
|
-
#
|
|
1
|
+
# bxo
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A fast, lightweight web framework for Bun with built-in Zod validation and lifecycle hooks.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Features
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
- 🎮 **Server Management** - Programmatic start, stop, and restart capabilities
|
|
14
|
-
- 📊 **Status Monitoring** - Built-in server status and runtime statistics
|
|
15
|
-
- 📦 **Zero Dependencies** - Only depends on Zod for validation
|
|
16
|
-
- ⚡ **Fast** - Minimal overhead with efficient routing
|
|
7
|
+
- **Type-safe routing** with Zod schema validation
|
|
8
|
+
- **WebSocket support** with clean API
|
|
9
|
+
- **Lifecycle hooks** for middleware and plugins
|
|
10
|
+
- **Plugin system** for extending functionality
|
|
11
|
+
- **Built-in CORS support** via plugin
|
|
12
|
+
- **Fast performance** leveraging Bun's native HTTP server
|
|
17
13
|
|
|
18
|
-
##
|
|
19
|
-
|
|
20
|
-
### Installation
|
|
14
|
+
## Installation
|
|
21
15
|
|
|
22
16
|
```bash
|
|
23
|
-
bun
|
|
17
|
+
bun install
|
|
24
18
|
```
|
|
25
19
|
|
|
26
|
-
|
|
20
|
+
## Quick Start
|
|
27
21
|
|
|
28
22
|
```typescript
|
|
29
|
-
import BXO
|
|
30
|
-
|
|
31
|
-
const app = new BXO()
|
|
32
|
-
.get('/', async (ctx) => {
|
|
33
|
-
return { message: 'Hello, BXO!' };
|
|
34
|
-
})
|
|
35
|
-
.start(3000);
|
|
36
|
-
```
|
|
23
|
+
import BXO from "./src/index";
|
|
24
|
+
import { cors } from "./plugins";
|
|
37
25
|
|
|
38
|
-
|
|
26
|
+
const app = new BXO();
|
|
39
27
|
|
|
40
|
-
|
|
28
|
+
// Use CORS plugin
|
|
29
|
+
app.use(cors());
|
|
41
30
|
|
|
42
|
-
|
|
31
|
+
// Define routes
|
|
32
|
+
app.get("/", (ctx) => ctx.json({ message: "Hello World!" }));
|
|
43
33
|
|
|
44
|
-
|
|
45
|
-
const app = new BXO()
|
|
46
|
-
// Simple handler
|
|
47
|
-
.get('/simple', async (ctx) => {
|
|
48
|
-
return { message: 'Hello World' };
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
// With validation
|
|
52
|
-
.post('/users', async (ctx) => {
|
|
53
|
-
// ctx.body is fully typed
|
|
54
|
-
return { created: ctx.body };
|
|
55
|
-
}, {
|
|
56
|
-
body: z.object({
|
|
57
|
-
name: z.string(),
|
|
58
|
-
email: z.string().email()
|
|
59
|
-
})
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
// Path parameters
|
|
63
|
-
.get('/users/:id', async (ctx) => {
|
|
64
|
-
// ctx.params.id is typed as UUID string
|
|
65
|
-
return { user: { id: ctx.params.id } };
|
|
66
|
-
}, {
|
|
67
|
-
params: z.object({
|
|
68
|
-
id: z.string().uuid()
|
|
69
|
-
}),
|
|
70
|
-
query: z.object({
|
|
71
|
-
include: z.string().optional()
|
|
72
|
-
})
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
// All HTTP methods supported
|
|
76
|
-
.put('/users/:id', handler)
|
|
77
|
-
.delete('/users/:id', handler)
|
|
78
|
-
.patch('/users/:id', handler);
|
|
34
|
+
app.start();
|
|
79
35
|
```
|
|
80
36
|
|
|
81
|
-
|
|
37
|
+
## WebSocket Support
|
|
82
38
|
|
|
83
|
-
|
|
39
|
+
BXO provides built-in WebSocket support with a clean, intuitive API:
|
|
84
40
|
|
|
85
41
|
```typescript
|
|
86
|
-
|
|
87
|
-
params: InferredParamsType; // Path parameters
|
|
88
|
-
query: InferredQueryType; // Query string parameters
|
|
89
|
-
body: InferredBodyType; // Request body
|
|
90
|
-
headers: InferredHeadersType; // Request headers
|
|
91
|
-
request: Request; // Original Request object
|
|
92
|
-
set: { // Response configuration
|
|
93
|
-
status?: number;
|
|
94
|
-
headers?: Record<string, string>;
|
|
95
|
-
};
|
|
96
|
-
user?: any; // Added by auth plugin
|
|
97
|
-
[key: string]: any; // Extended by plugins
|
|
98
|
-
}
|
|
99
|
-
```
|
|
42
|
+
import BXO from "./src";
|
|
100
43
|
|
|
101
|
-
|
|
44
|
+
const app = new BXO();
|
|
102
45
|
|
|
103
|
-
|
|
46
|
+
// WebSocket route
|
|
47
|
+
app.ws("/ws", {
|
|
48
|
+
open(ws) {
|
|
49
|
+
console.log("WebSocket connection opened");
|
|
50
|
+
ws.send("Welcome to BXO WebSocket!");
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
message(ws, message) {
|
|
54
|
+
console.log("Received message:", message);
|
|
55
|
+
// Echo the message back
|
|
56
|
+
ws.send(`Echo: ${message}`);
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
close(ws, code, reason) {
|
|
60
|
+
console.log(`WebSocket connection closed: ${code} ${reason}`);
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
ping(ws, data) {
|
|
64
|
+
console.log("Ping received:", data);
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
pong(ws, data) {
|
|
68
|
+
console.log("Pong received:", data);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// WebSocket with path parameters
|
|
73
|
+
app.ws("/chat/:room", {
|
|
74
|
+
open(ws) {
|
|
75
|
+
const room = ws.data?.room || 'unknown';
|
|
76
|
+
console.log(`WebSocket connection opened for room: ${room}`);
|
|
77
|
+
ws.send(`Welcome to chat room: ${room}`);
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
message(ws, message) {
|
|
81
|
+
const room = ws.data?.room || 'unknown';
|
|
82
|
+
console.log(`Message in room ${room}:`, message);
|
|
83
|
+
ws.send(`[${room}] Echo: ${message}`);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
104
86
|
|
|
105
|
-
|
|
106
|
-
const config = {
|
|
107
|
-
params: z.object({ id: z.string().uuid() }),
|
|
108
|
-
query: z.object({
|
|
109
|
-
page: z.coerce.number().default(1),
|
|
110
|
-
limit: z.coerce.number().max(100).default(10)
|
|
111
|
-
}),
|
|
112
|
-
body: z.object({
|
|
113
|
-
name: z.string().min(1),
|
|
114
|
-
email: z.string().email()
|
|
115
|
-
}),
|
|
116
|
-
headers: z.object({
|
|
117
|
-
'content-type': z.literal('application/json')
|
|
118
|
-
})
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
app.post('/api/users/:id', async (ctx) => {
|
|
122
|
-
// All properties are fully typed based on schemas
|
|
123
|
-
const { id } = ctx.params; // string (UUID)
|
|
124
|
-
const { page, limit } = ctx.query; // number, number
|
|
125
|
-
const { name, email } = ctx.body; // string, string
|
|
126
|
-
}, config);
|
|
87
|
+
app.start();
|
|
127
88
|
```
|
|
128
89
|
|
|
129
|
-
|
|
90
|
+
### WebSocket Handler Events
|
|
130
91
|
|
|
131
|
-
|
|
92
|
+
- `open(ws)` - Called when a WebSocket connection is established
|
|
93
|
+
- `message(ws, message)` - Called when a message is received
|
|
94
|
+
- `close(ws, code, reason)` - Called when the connection is closed
|
|
95
|
+
- `drain(ws)` - Called when the WebSocket is ready for more data
|
|
96
|
+
- `ping(ws, data)` - Called when a ping is received
|
|
97
|
+
- `pong(ws, data)` - Called when a pong is received
|
|
132
98
|
|
|
133
|
-
###
|
|
99
|
+
### WebSocket Features
|
|
134
100
|
|
|
135
|
-
|
|
101
|
+
- **Path parameters** - Support for dynamic routes like `/chat/:room`
|
|
102
|
+
- **Automatic upgrade** - HTTP requests to WebSocket routes are automatically upgraded
|
|
103
|
+
- **Type safety** - Full TypeScript support with proper typing
|
|
104
|
+
- **Error handling** - Built-in error handling for WebSocket events
|
|
105
|
+
- **Data attachment** - Access to path information via `ws.data`
|
|
136
106
|
|
|
137
|
-
|
|
138
|
-
import { cors } from './plugins';
|
|
107
|
+
## Lifecycle Hooks
|
|
139
108
|
|
|
140
|
-
|
|
141
|
-
origin: ['http://localhost:3000', 'https://example.com'],
|
|
142
|
-
methods: ['GET', 'POST', 'PUT', 'DELETE'],
|
|
143
|
-
allowedHeaders: ['Content-Type', 'Authorization'],
|
|
144
|
-
credentials: true,
|
|
145
|
-
maxAge: 86400
|
|
146
|
-
}));
|
|
147
|
-
```
|
|
109
|
+
BXO provides powerful lifecycle hooks that allow you to intercept and modify requests and responses at different stages:
|
|
148
110
|
|
|
149
|
-
|
|
111
|
+
### beforeRequest
|
|
112
|
+
Runs before any route processing. Can modify the request or return a response to short-circuit processing.
|
|
150
113
|
|
|
151
114
|
```typescript
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
includeBody: false, // Log request/response bodies
|
|
157
|
-
includeHeaders: false // Log headers
|
|
158
|
-
}));
|
|
115
|
+
app.beforeRequest(async (req) => {
|
|
116
|
+
console.log(`${req.method} ${req.url}`);
|
|
117
|
+
return req; // Continue with request
|
|
118
|
+
});
|
|
159
119
|
```
|
|
160
120
|
|
|
161
|
-
|
|
121
|
+
### afterRequest
|
|
122
|
+
Runs after route processing but before the response is sent. Can modify the final response.
|
|
162
123
|
|
|
163
124
|
```typescript
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
secret: 'your-secret-key',
|
|
169
|
-
exclude: ['/login', '/health'], // Skip auth for these paths
|
|
170
|
-
verify: async (token, ctx) => {
|
|
171
|
-
// Custom token verification
|
|
172
|
-
return { user: 'data' };
|
|
173
|
-
}
|
|
174
|
-
}));
|
|
175
|
-
|
|
176
|
-
// Create JWT tokens
|
|
177
|
-
const token = createJWT(
|
|
178
|
-
{ userId: 123, role: 'admin' },
|
|
179
|
-
'secret',
|
|
180
|
-
3600 // expires in 1 hour
|
|
181
|
-
);
|
|
125
|
+
app.afterRequest(async (req, res) => {
|
|
126
|
+
res.headers.set("X-Response-Time", Date.now().toString());
|
|
127
|
+
return res;
|
|
128
|
+
});
|
|
182
129
|
```
|
|
183
130
|
|
|
184
|
-
|
|
131
|
+
### beforeResponse
|
|
132
|
+
Runs after the route handler but before response headers are merged. Useful for modifying response metadata.
|
|
185
133
|
|
|
186
134
|
```typescript
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
window: 60, // Time window in seconds
|
|
192
|
-
exclude: ['/health'], // Skip rate limiting for these paths
|
|
193
|
-
keyGenerator: (ctx) => { // Custom key generation
|
|
194
|
-
return ctx.request.headers.get('x-api-key') || 'default';
|
|
195
|
-
},
|
|
196
|
-
message: 'Too many requests',
|
|
197
|
-
statusCode: 429
|
|
198
|
-
}));
|
|
135
|
+
app.beforeResponse(async (res) => {
|
|
136
|
+
res.headers.set("X-Custom-Header", "value");
|
|
137
|
+
return res;
|
|
138
|
+
});
|
|
199
139
|
```
|
|
200
140
|
|
|
201
|
-
###
|
|
141
|
+
### onError
|
|
142
|
+
Runs when an error occurs during request processing. Can return a custom error response.
|
|
202
143
|
|
|
203
144
|
```typescript
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
ctx.startTime = Date.now();
|
|
209
|
-
},
|
|
210
|
-
onResponse: async (ctx, response) => {
|
|
211
|
-
console.log(`Request took ${Date.now() - ctx.startTime}ms`);
|
|
212
|
-
return response;
|
|
213
|
-
},
|
|
214
|
-
onError: async (ctx, error) => {
|
|
215
|
-
console.error('Request failed:', error.message);
|
|
216
|
-
return { error: 'Custom error response' };
|
|
217
|
-
}
|
|
218
|
-
};
|
|
219
|
-
|
|
220
|
-
app.use(customPlugin);
|
|
145
|
+
app.onError(async (error, req) => {
|
|
146
|
+
console.error(`Error: ${error.message}`);
|
|
147
|
+
return new Response("Internal Server Error", { status: 500 });
|
|
148
|
+
});
|
|
221
149
|
```
|
|
222
150
|
|
|
223
|
-
##
|
|
151
|
+
## Plugins
|
|
224
152
|
|
|
225
|
-
|
|
153
|
+
### CORS Plugin
|
|
226
154
|
|
|
227
|
-
|
|
155
|
+
The CORS plugin provides comprehensive Cross-Origin Resource Sharing support:
|
|
228
156
|
|
|
229
157
|
```typescript
|
|
230
|
-
|
|
231
|
-
.onBeforeStart(() => {
|
|
232
|
-
console.log('🔧 Preparing to start server...');
|
|
233
|
-
})
|
|
234
|
-
.onAfterStart(() => {
|
|
235
|
-
console.log('✅ Server fully started and ready!');
|
|
236
|
-
})
|
|
237
|
-
.onBeforeStop(() => {
|
|
238
|
-
console.log('🔧 Preparing to stop server...');
|
|
239
|
-
})
|
|
240
|
-
.onAfterStop(() => {
|
|
241
|
-
console.log('✅ Server fully stopped!');
|
|
242
|
-
})
|
|
243
|
-
.onBeforeRestart(() => {
|
|
244
|
-
console.log('🔧 Preparing to restart server...');
|
|
245
|
-
})
|
|
246
|
-
.onAfterRestart(() => {
|
|
247
|
-
console.log('✅ Server restart completed!');
|
|
248
|
-
});
|
|
249
|
-
```
|
|
158
|
+
import { cors } from "./plugins";
|
|
250
159
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
console.log(`📨 ${ctx.request.method} ${ctx.request.url}`);
|
|
257
|
-
})
|
|
258
|
-
.onResponse((ctx, response) => {
|
|
259
|
-
console.log(`📤 Response sent`);
|
|
260
|
-
return response; // Can modify response
|
|
261
|
-
})
|
|
262
|
-
.onError((ctx, error) => {
|
|
263
|
-
console.error(`💥 Error:`, error.message);
|
|
264
|
-
return { error: 'Something went wrong' }; // Can provide custom error response
|
|
265
|
-
});
|
|
160
|
+
app.use(cors({
|
|
161
|
+
origin: ["http://localhost:3000", "https://myapp.com"],
|
|
162
|
+
methods: ["GET", "POST", "PUT", "DELETE"],
|
|
163
|
+
credentials: true
|
|
164
|
+
}));
|
|
266
165
|
```
|
|
267
166
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
BXO includes built-in hot reload and comprehensive server management capabilities:
|
|
167
|
+
### OpenAPI Plugin
|
|
271
168
|
|
|
272
|
-
|
|
169
|
+
The OpenAPI plugin automatically generates OpenAPI 3.0 documentation with support for tags, security schemes, and comprehensive route metadata:
|
|
273
170
|
|
|
274
171
|
```typescript
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
//
|
|
281
|
-
//
|
|
282
|
-
|
|
172
|
+
import { openapi } from "./plugins";
|
|
173
|
+
|
|
174
|
+
app.use(openapi({
|
|
175
|
+
path: "/docs", // Swagger UI endpoint
|
|
176
|
+
jsonPath: "/openapi.json", // OpenAPI JSON endpoint
|
|
177
|
+
defaultTags: ["API"], // Default tags for routes
|
|
178
|
+
securitySchemes: { // Define security schemes
|
|
179
|
+
bearerAuth: {
|
|
180
|
+
type: "http",
|
|
181
|
+
scheme: "bearer",
|
|
182
|
+
bearerFormat: "JWT",
|
|
183
|
+
description: "JWT token for authentication"
|
|
184
|
+
},
|
|
185
|
+
apiKeyAuth: {
|
|
186
|
+
type: "apiKey",
|
|
187
|
+
in: "header",
|
|
188
|
+
name: "X-API-Key",
|
|
189
|
+
description: "API key for authentication"
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
globalSecurity: [ // Global security requirements
|
|
193
|
+
{ bearerAuth: [] },
|
|
194
|
+
{ apiKeyAuth: [] }
|
|
195
|
+
],
|
|
196
|
+
openapiConfig: { // Additional OpenAPI config
|
|
197
|
+
info: {
|
|
198
|
+
title: "My API",
|
|
199
|
+
version: "1.0.0",
|
|
200
|
+
description: "API description"
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}));
|
|
283
204
|
```
|
|
284
205
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
```typescript
|
|
288
|
-
const app = new BXO();
|
|
206
|
+
#### Route Metadata
|
|
289
207
|
|
|
290
|
-
|
|
291
|
-
await app.start(3000, 'localhost');
|
|
292
|
-
|
|
293
|
-
// Check if server is running
|
|
294
|
-
if (app.isServerRunning()) {
|
|
295
|
-
console.log('Server is running!');
|
|
296
|
-
}
|
|
208
|
+
Routes can include detailed metadata for better OpenAPI documentation:
|
|
297
209
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
//
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
//
|
|
309
|
-
|
|
210
|
+
```typescript
|
|
211
|
+
app.get("/users/:id", (ctx) => {
|
|
212
|
+
const id = ctx.params.id
|
|
213
|
+
return { user: { id, name: "John Doe" } }
|
|
214
|
+
}, {
|
|
215
|
+
detail: {
|
|
216
|
+
tags: ["Users"], // Route tags for grouping
|
|
217
|
+
summary: "Get user by ID", // Operation summary
|
|
218
|
+
description: "Retrieve user details", // Detailed description
|
|
219
|
+
security: [{ bearerAuth: [] }], // Route-specific security
|
|
220
|
+
params: { // Parameter documentation
|
|
221
|
+
id: z.string().describe("User ID")
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
})
|
|
310
225
|
```
|
|
311
226
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
```typescript
|
|
315
|
-
const app = new BXO();
|
|
227
|
+
#### Supported Metadata Fields
|
|
316
228
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
229
|
+
- `tags`: Array of tags for grouping operations
|
|
230
|
+
- `summary`: Short description of the operation
|
|
231
|
+
- `description`: Detailed description of the operation
|
|
232
|
+
- `security`: Security requirements for the route
|
|
233
|
+
- `params`: Path parameter schemas and descriptions
|
|
234
|
+
- `query`: Query parameter schemas
|
|
235
|
+
- `hidden`: Set to `true` to exclude from OpenAPI docs
|
|
321
236
|
|
|
322
|
-
|
|
323
|
-
if (process.env.NODE_ENV === 'development') {
|
|
324
|
-
app.post('/dev/restart', async (ctx) => {
|
|
325
|
-
setTimeout(() => app.restart(3000), 100);
|
|
326
|
-
return { message: 'Server restart initiated' };
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
app.get('/dev/status', async (ctx) => {
|
|
330
|
-
return {
|
|
331
|
-
...app.getServerInfo(),
|
|
332
|
-
uptime: process.uptime(),
|
|
333
|
-
memory: process.memoryUsage()
|
|
334
|
-
};
|
|
335
|
-
});
|
|
336
|
-
}
|
|
337
|
-
```
|
|
237
|
+
### Creating Custom Plugins
|
|
338
238
|
|
|
339
|
-
|
|
239
|
+
Plugins are just BXO instances with lifecycle hooks:
|
|
340
240
|
|
|
341
241
|
```typescript
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
const app = new BXO();
|
|
346
|
-
|
|
347
|
-
// Enable hot reload for development
|
|
348
|
-
app.enableHotReload(['./']);
|
|
349
|
-
|
|
350
|
-
// Add plugins
|
|
351
|
-
app
|
|
352
|
-
.use(logger({ format: 'simple' }))
|
|
353
|
-
.use(cors({
|
|
354
|
-
origin: ['http://localhost:3000'],
|
|
355
|
-
credentials: true
|
|
356
|
-
}))
|
|
357
|
-
.use(rateLimit({
|
|
358
|
-
max: 100,
|
|
359
|
-
window: 60,
|
|
360
|
-
exclude: ['/health']
|
|
361
|
-
}))
|
|
362
|
-
.use(auth({
|
|
363
|
-
type: 'jwt',
|
|
364
|
-
secret: 'your-secret-key',
|
|
365
|
-
exclude: ['/', '/login', '/health']
|
|
366
|
-
}));
|
|
367
|
-
|
|
368
|
-
// Comprehensive lifecycle hooks
|
|
369
|
-
app
|
|
370
|
-
.onBeforeStart(() => console.log('🔧 Preparing server startup...'))
|
|
371
|
-
.onAfterStart(() => console.log('✅ Server ready!'))
|
|
372
|
-
.onBeforeRestart(() => console.log('🔄 Restarting server...'))
|
|
373
|
-
.onAfterRestart(() => console.log('✅ Server restarted!'))
|
|
374
|
-
.onError((ctx, error) => ({
|
|
375
|
-
error: 'Internal server error',
|
|
376
|
-
timestamp: new Date().toISOString()
|
|
377
|
-
}));
|
|
378
|
-
|
|
379
|
-
// Routes
|
|
380
|
-
app
|
|
381
|
-
.get('/health', async () => ({
|
|
382
|
-
status: 'ok',
|
|
383
|
-
timestamp: new Date().toISOString(),
|
|
384
|
-
server: app.getServerInfo()
|
|
385
|
-
}))
|
|
386
|
-
|
|
387
|
-
.post('/login', async (ctx) => {
|
|
388
|
-
const { username, password } = ctx.body;
|
|
389
|
-
|
|
390
|
-
if (username === 'admin' && password === 'password') {
|
|
391
|
-
const token = createJWT(
|
|
392
|
-
{ username, role: 'admin' },
|
|
393
|
-
'your-secret-key'
|
|
394
|
-
);
|
|
395
|
-
return { token, user: { username, role: 'admin' } };
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
ctx.set.status = 401;
|
|
399
|
-
return { error: 'Invalid credentials' };
|
|
400
|
-
}, {
|
|
401
|
-
body: z.object({
|
|
402
|
-
username: z.string(),
|
|
403
|
-
password: z.string()
|
|
404
|
-
})
|
|
405
|
-
})
|
|
406
|
-
|
|
407
|
-
.get('/users/:id', async (ctx) => {
|
|
408
|
-
return {
|
|
409
|
-
user: {
|
|
410
|
-
id: ctx.params.id,
|
|
411
|
-
include: ctx.query.include
|
|
412
|
-
}
|
|
413
|
-
};
|
|
414
|
-
}, {
|
|
415
|
-
params: z.object({ id: z.string().uuid() }),
|
|
416
|
-
query: z.object({ include: z.string().optional() })
|
|
417
|
-
})
|
|
418
|
-
|
|
419
|
-
.post('/users', async (ctx) => {
|
|
420
|
-
return { created: ctx.body };
|
|
421
|
-
}, {
|
|
422
|
-
body: z.object({
|
|
423
|
-
name: z.string(),
|
|
424
|
-
email: z.string().email()
|
|
425
|
-
})
|
|
426
|
-
})
|
|
427
|
-
|
|
428
|
-
.get('/protected', async (ctx) => {
|
|
429
|
-
// ctx.user available from auth plugin
|
|
430
|
-
return {
|
|
431
|
-
message: 'Protected resource',
|
|
432
|
-
user: ctx.user
|
|
433
|
-
};
|
|
434
|
-
})
|
|
242
|
+
function loggingPlugin() {
|
|
243
|
+
const plugin = new BXO();
|
|
435
244
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
return { message: 'Server restart initiated' };
|
|
440
|
-
})
|
|
441
|
-
|
|
442
|
-
.get('/status', async (ctx) => {
|
|
443
|
-
return {
|
|
444
|
-
...app.getServerInfo(),
|
|
445
|
-
uptime: process.uptime(),
|
|
446
|
-
memory: process.memoryUsage()
|
|
447
|
-
};
|
|
245
|
+
plugin.beforeRequest(async (req) => {
|
|
246
|
+
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
|
|
247
|
+
return req;
|
|
448
248
|
});
|
|
249
|
+
|
|
250
|
+
return plugin;
|
|
251
|
+
}
|
|
449
252
|
|
|
450
|
-
|
|
451
|
-
app.start(3000);
|
|
253
|
+
app.use(loggingPlugin());
|
|
452
254
|
```
|
|
453
255
|
|
|
454
|
-
##
|
|
455
|
-
|
|
456
|
-
With the example server running, test these endpoints:
|
|
457
|
-
|
|
458
|
-
```bash
|
|
459
|
-
# Health check
|
|
460
|
-
curl http://localhost:3000/health
|
|
461
|
-
|
|
462
|
-
# Login to get token
|
|
463
|
-
curl -X POST http://localhost:3000/login \
|
|
464
|
-
-H "Content-Type: application/json" \
|
|
465
|
-
-d '{"username": "admin", "password": "password"}'
|
|
466
|
-
|
|
467
|
-
# Create user
|
|
468
|
-
curl -X POST http://localhost:3000/users \
|
|
469
|
-
-H "Content-Type: application/json" \
|
|
470
|
-
-d '{"name": "John Doe", "email": "john@example.com"}'
|
|
256
|
+
## Route Validation
|
|
471
257
|
|
|
472
|
-
|
|
473
|
-
curl "http://localhost:3000/users/123e4567-e89b-12d3-a456-426614174000?include=profile"
|
|
258
|
+
Define Zod schemas for request validation:
|
|
474
259
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
260
|
+
```typescript
|
|
261
|
+
import { z } from "bxo";
|
|
262
|
+
|
|
263
|
+
const UserSchema = z.object({
|
|
264
|
+
name: z.string().min(1),
|
|
265
|
+
email: z.string().email()
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
app.post("/users", async (ctx) => {
|
|
269
|
+
const user = ctx.body; // Already validated by UserSchema
|
|
270
|
+
return ctx.json({ id: 1, ...user });
|
|
271
|
+
}, {
|
|
272
|
+
body: UserSchema
|
|
273
|
+
});
|
|
274
|
+
```
|
|
478
275
|
|
|
479
|
-
|
|
480
|
-
curl http://localhost:3000/status
|
|
276
|
+
## Multipart/Form-Data Parsing
|
|
481
277
|
|
|
482
|
-
|
|
483
|
-
curl -X POST http://localhost:3000/restart
|
|
484
|
-
```
|
|
278
|
+
BXO automatically parses multipart/form-data into nested objects and arrays before Zod validation:
|
|
485
279
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
### BXO Class Methods
|
|
489
|
-
|
|
490
|
-
#### HTTP Methods
|
|
491
|
-
- `get(path, handler, config?)` - Handle GET requests
|
|
492
|
-
- `post(path, handler, config?)` - Handle POST requests
|
|
493
|
-
- `put(path, handler, config?)` - Handle PUT requests
|
|
494
|
-
- `delete(path, handler, config?)` - Handle DELETE requests
|
|
495
|
-
- `patch(path, handler, config?)` - Handle PATCH requests
|
|
496
|
-
|
|
497
|
-
#### Plugins & Hooks
|
|
498
|
-
- `use(plugin)` - Add a plugin
|
|
499
|
-
- `onBeforeStart(handler)` - Before server start hook
|
|
500
|
-
- `onAfterStart(handler)` - After server start hook
|
|
501
|
-
- `onBeforeStop(handler)` - Before server stop hook
|
|
502
|
-
- `onAfterStop(handler)` - After server stop hook
|
|
503
|
-
- `onBeforeRestart(handler)` - Before server restart hook
|
|
504
|
-
- `onAfterRestart(handler)` - After server restart hook
|
|
505
|
-
- `onRequest(handler)` - Global request hook
|
|
506
|
-
- `onResponse(handler)` - Global response hook
|
|
507
|
-
- `onError(handler)` - Global error hook
|
|
508
|
-
|
|
509
|
-
#### Server Management
|
|
510
|
-
- `start(port?, hostname?)` - Start the server
|
|
511
|
-
- `stop()` - Stop the server gracefully
|
|
512
|
-
- `restart(port?, hostname?)` - Restart the server
|
|
513
|
-
- `listen(port?, hostname?)` - Start the server (backward compatibility)
|
|
514
|
-
- `isServerRunning()` - Check if server is running
|
|
515
|
-
- `getServerInfo()` - Get server status information
|
|
516
|
-
|
|
517
|
-
#### Hot Reload
|
|
518
|
-
- `enableHotReload(watchPaths?)` - Enable hot reload with file watching
|
|
519
|
-
|
|
520
|
-
### Route Configuration
|
|
280
|
+
### Nested Objects
|
|
281
|
+
Form fields like `profile[name]` are automatically converted to nested objects:
|
|
521
282
|
|
|
522
283
|
```typescript
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
}
|
|
284
|
+
const UserFormSchema = z.object({
|
|
285
|
+
name: z.string(),
|
|
286
|
+
email: z.string().email(),
|
|
287
|
+
profile: z.object({
|
|
288
|
+
name: z.string()
|
|
289
|
+
})
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
app.post("/users", async (ctx) => {
|
|
293
|
+
// Form data: profile[name]="John" becomes { profile: { name: "John" } }
|
|
294
|
+
console.log(ctx.body); // { name: "...", email: "...", profile: { name: "John" } }
|
|
295
|
+
return ctx.json({ success: true, data: ctx.body });
|
|
296
|
+
}, {
|
|
297
|
+
body: UserFormSchema
|
|
298
|
+
});
|
|
529
299
|
```
|
|
530
300
|
|
|
531
|
-
###
|
|
301
|
+
### Arrays
|
|
302
|
+
Form fields like `items[0]`, `items[1]` are automatically converted to arrays:
|
|
532
303
|
|
|
533
304
|
```typescript
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
305
|
+
const ItemsSchema = z.object({
|
|
306
|
+
items: z.array(z.string()),
|
|
307
|
+
tags: z.array(z.string()),
|
|
308
|
+
profile: z.object({
|
|
309
|
+
name: z.string(),
|
|
310
|
+
age: z.string().transform(val => parseInt(val, 10))
|
|
311
|
+
})
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
app.post("/items", async (ctx) => {
|
|
315
|
+
// Form data: items[0]="Apple", items[1]="Banana" becomes { items: ["Apple", "Banana"] }
|
|
316
|
+
console.log(ctx.body); // { items: ["Apple", "Banana"], tags: [...], profile: {...} }
|
|
317
|
+
return ctx.json({ success: true, data: ctx.body });
|
|
318
|
+
}, {
|
|
319
|
+
body: ItemsSchema
|
|
320
|
+
});
|
|
540
321
|
```
|
|
541
322
|
|
|
542
|
-
|
|
323
|
+
### Supported Patterns
|
|
324
|
+
- **Nested objects**: `profile[name]`, `settings[theme]` → `{ profile: { name: "..." }, settings: { theme: "..." } }`
|
|
325
|
+
- **Arrays**: `items[0]`, `items[1]` → `{ items: ["...", "..."] }`
|
|
326
|
+
- **Duplicate keys**: Multiple values with same key → `{ tags: ["tag1", "tag2"] }`
|
|
543
327
|
|
|
544
|
-
|
|
328
|
+
## Running
|
|
545
329
|
|
|
546
330
|
```bash
|
|
547
|
-
|
|
548
|
-
bun run example.ts
|
|
549
|
-
|
|
550
|
-
# The server will automatically restart when you edit any .ts/.js files!
|
|
331
|
+
bun run ./src/index.ts
|
|
551
332
|
```
|
|
552
333
|
|
|
553
|
-
|
|
334
|
+
Or run the examples:
|
|
554
335
|
|
|
555
|
-
```
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
├── plugins/
|
|
559
|
-
│ ├── index.ts # Plugin exports
|
|
560
|
-
│ ├── cors.ts # CORS plugin
|
|
561
|
-
│ ├── logger.ts # Logger plugin
|
|
562
|
-
│ ├── auth.ts # Authentication plugin
|
|
563
|
-
│ └── ratelimit.ts # Rate limiting plugin
|
|
564
|
-
├── example.ts # Usage example
|
|
565
|
-
├── package.json
|
|
566
|
-
└── README.md
|
|
567
|
-
```
|
|
568
|
-
|
|
569
|
-
## 🤝 Contributing
|
|
336
|
+
```bash
|
|
337
|
+
# CORS example
|
|
338
|
+
bun run ./example/cors-example.ts
|
|
570
339
|
|
|
571
|
-
|
|
340
|
+
# Multipart form data parsing example
|
|
341
|
+
bun run ./example/multipart-example.ts
|
|
342
|
+
```
|
|
572
343
|
|
|
573
|
-
##
|
|
344
|
+
## Examples
|
|
574
345
|
|
|
575
|
-
|
|
346
|
+
Check out the `example/` directory for more usage examples:
|
|
576
347
|
|
|
577
|
-
|
|
348
|
+
- `cors-example.ts` - Demonstrates CORS plugin and lifecycle hooks
|
|
349
|
+
- `openapi-example.ts` - Demonstrates OpenAPI plugin with tags and security
|
|
350
|
+
- `websocket-example.ts` - Demonstrates WebSocket functionality with interactive HTML client
|
|
351
|
+
- `multipart-example.ts` - Demonstrates multipart/form-data parsing with nested objects and arrays
|
|
352
|
+
- `index.ts` - Basic routing example
|
|
578
353
|
|
|
579
|
-
|
|
354
|
+
This project was created using `bun init` in bun v1.2.3. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
|