@minejs/server 0.0.1
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/LICENSE +21 -0
- package/README.md +849 -0
- package/dist/main.cjs +3 -0
- package/dist/main.cjs.map +1 -0
- package/dist/main.d.cts +511 -0
- package/dist/main.d.ts +511 -0
- package/dist/main.js +3 -0
- package/dist/main.js.map +1 -0
- package/package.json +57 -0
package/README.md
ADDED
|
@@ -0,0 +1,849 @@
|
|
|
1
|
+
<!-- ╔══════════════════════════════ BEG ══════════════════════════════╗ -->
|
|
2
|
+
|
|
3
|
+
<br>
|
|
4
|
+
<div align="center">
|
|
5
|
+
<p>
|
|
6
|
+
<img src="./assets/img/logo.png" alt="logo" style="" height="60" />
|
|
7
|
+
</p>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<div align="center">
|
|
11
|
+
<img src="https://img.shields.io/badge/v-0.0.1-black"/>
|
|
12
|
+
<img src="https://img.shields.io/badge/🔥-@minejs-black"/>
|
|
13
|
+
<br>
|
|
14
|
+
<img src="https://img.shields.io/badge/coverage-94.71%25-brightgreen" alt="Test Coverage" />
|
|
15
|
+
<img src="https://img.shields.io/github/issues/minejs/server?style=flat" alt="Github Repo Issues" />
|
|
16
|
+
<img src="https://img.shields.io/github/stars/minejs/server?style=social" alt="GitHub Repo stars" />
|
|
17
|
+
</div>
|
|
18
|
+
<br>
|
|
19
|
+
|
|
20
|
+
<!-- ╚═════════════════════════════════════════════════════════════════╝ -->
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
<!-- ╔══════════════════════════════ DOC ══════════════════════════════╗ -->
|
|
25
|
+
|
|
26
|
+
- ## Quick Start 🔥
|
|
27
|
+
|
|
28
|
+
> **_A lightweight, type-safe HTTP server framework for Bun with built-in security, routing, and database support._**
|
|
29
|
+
|
|
30
|
+
- ### Setup
|
|
31
|
+
|
|
32
|
+
> install [`space`](https://github.com/solution-lib/space) first.
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
space i @minejs/server
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
<div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
|
|
39
|
+
|
|
40
|
+
- ### Usage
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
import { server, type ServerInstance, type AppContext } from '@minejs/server'
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
- ### 1. Basic Server
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
import { server } from '@minejs/server'
|
|
50
|
+
|
|
51
|
+
const app = server({
|
|
52
|
+
port: 3000,
|
|
53
|
+
logging: true,
|
|
54
|
+
routes: [
|
|
55
|
+
{
|
|
56
|
+
method: 'GET',
|
|
57
|
+
path: '/hello',
|
|
58
|
+
handler: (c) => c.json({ message: 'Hello, World!' })
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
await app.start()
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
- ### 2. Route Handling with Parameters
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
import { server, type AppContext } from '@minejs/server'
|
|
70
|
+
|
|
71
|
+
const app = server({
|
|
72
|
+
port: 3000,
|
|
73
|
+
routes: [
|
|
74
|
+
{
|
|
75
|
+
method: 'GET',
|
|
76
|
+
path: '/users/:id',
|
|
77
|
+
handler: (c: AppContext) => {
|
|
78
|
+
const userId = c.params.id
|
|
79
|
+
return c.json({ userId, name: 'John Doe' })
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
method: 'POST',
|
|
84
|
+
path: '/users',
|
|
85
|
+
handler: (c: AppContext) => {
|
|
86
|
+
const body = c.body
|
|
87
|
+
return c.json({ created: true, data: body }, 201)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
]
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
await app.start()
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
- ### 3. Request Context (AppContext)
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
import { server, type AppContext } from '@minejs/server'
|
|
100
|
+
|
|
101
|
+
const app = server({
|
|
102
|
+
port: 3000,
|
|
103
|
+
routes: [
|
|
104
|
+
{
|
|
105
|
+
method: 'GET',
|
|
106
|
+
path: '/context-demo',
|
|
107
|
+
handler: (c: AppContext) => {
|
|
108
|
+
return c.json({
|
|
109
|
+
ip: c.ip, // Client IP
|
|
110
|
+
method: c.request.method, // HTTP method
|
|
111
|
+
path: c.request.url, // Request URL
|
|
112
|
+
lang: c.lang, // Request language
|
|
113
|
+
requestId: c.requestId, // Unique request ID
|
|
114
|
+
query: c.query, // Query parameters
|
|
115
|
+
headers: Object.fromEntries(c.headers.entries())
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
]
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
await app.start()
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
- ### 4. Middleware
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
import { server, type AppContext, type AppMiddleware } from '@minejs/server'
|
|
129
|
+
|
|
130
|
+
// Custom middleware
|
|
131
|
+
const authMiddleware: AppMiddleware = async (c, next) => {
|
|
132
|
+
const token = c.getHeader('Authorization')
|
|
133
|
+
|
|
134
|
+
if (!token) {
|
|
135
|
+
return c.json({ error: 'Unauthorized' }, 401)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Set user in context
|
|
139
|
+
c.user = { id: 1, name: 'John' }
|
|
140
|
+
|
|
141
|
+
// Continue to next middleware/handler
|
|
142
|
+
await next()
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const app = server({
|
|
146
|
+
port: 3000,
|
|
147
|
+
routes: [
|
|
148
|
+
{
|
|
149
|
+
method: 'GET',
|
|
150
|
+
path: '/protected',
|
|
151
|
+
handler: (c: AppContext) => {
|
|
152
|
+
return c.json({ user: c.user })
|
|
153
|
+
},
|
|
154
|
+
middlewares: [authMiddleware]
|
|
155
|
+
}
|
|
156
|
+
]
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
await app.start()
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
- ### 5. Security (CORS, Rate Limiting, Headers)
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
import { server } from '@minejs/server'
|
|
166
|
+
|
|
167
|
+
const app = server({
|
|
168
|
+
port: 3000,
|
|
169
|
+
logging: true,
|
|
170
|
+
security: {
|
|
171
|
+
cors: {
|
|
172
|
+
origin: ['https://example.com', 'https://app.example.com'],
|
|
173
|
+
credentials: true,
|
|
174
|
+
maxAge: 3600
|
|
175
|
+
},
|
|
176
|
+
rateLimit: {
|
|
177
|
+
windowMs: 60000, // 1 minute
|
|
178
|
+
max: 100, // 100 requests per minute
|
|
179
|
+
message: 'Too many requests'
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
routes: [
|
|
183
|
+
{
|
|
184
|
+
method: 'GET',
|
|
185
|
+
path: '/api/data',
|
|
186
|
+
handler: (c) => c.json({ data: [] })
|
|
187
|
+
}
|
|
188
|
+
]
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
await app.start()
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
- ### 6. Cookie Management
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
import { server, type AppContext } from '@minejs/server'
|
|
198
|
+
|
|
199
|
+
const app = server({
|
|
200
|
+
port: 3000,
|
|
201
|
+
routes: [
|
|
202
|
+
{
|
|
203
|
+
method: 'POST',
|
|
204
|
+
path: '/login',
|
|
205
|
+
handler: (c: AppContext) => {
|
|
206
|
+
// Set cookie
|
|
207
|
+
c.setCookie('session_id', 'abc123', {
|
|
208
|
+
maxAge: 3600, // 1 hour
|
|
209
|
+
httpOnly: true,
|
|
210
|
+
secure: true,
|
|
211
|
+
sameSite: 'Strict'
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
return c.json({ success: true })
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
method: 'GET',
|
|
219
|
+
path: '/profile',
|
|
220
|
+
handler: (c: AppContext) => {
|
|
221
|
+
// Get cookie
|
|
222
|
+
const sessionId = c.getCookie('session_id')
|
|
223
|
+
return c.json({ sessionId })
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
method: 'POST',
|
|
228
|
+
path: '/logout',
|
|
229
|
+
handler: (c: AppContext) => {
|
|
230
|
+
// Delete cookie
|
|
231
|
+
c.deleteCookie('session_id', { path: '/' })
|
|
232
|
+
return c.json({ success: true })
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
]
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
await app.start()
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
- ### 7. Static File Serving
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
import { server } from '@minejs/server'
|
|
245
|
+
|
|
246
|
+
const app = server({
|
|
247
|
+
port: 3000,
|
|
248
|
+
static: {
|
|
249
|
+
path: '/public', // URL prefix
|
|
250
|
+
directory: './public', // Local directory
|
|
251
|
+
maxAge: 3600, // Cache in seconds
|
|
252
|
+
dotfiles: 'deny', // Don't serve hidden files
|
|
253
|
+
index: ['index.html'] // Index files
|
|
254
|
+
}
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
await app.start()
|
|
258
|
+
// Now http://localhost:3000/public/style.css serves ./public/style.css
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
- ### 8. Database Integration
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
import { server, type AppContext } from '@minejs/server'
|
|
265
|
+
|
|
266
|
+
const app = server({
|
|
267
|
+
port: 3000,
|
|
268
|
+
database: {
|
|
269
|
+
connection: './data.db', // SQLite file path
|
|
270
|
+
name: 'default'
|
|
271
|
+
},
|
|
272
|
+
routes: [
|
|
273
|
+
{
|
|
274
|
+
method: 'GET',
|
|
275
|
+
path: '/users',
|
|
276
|
+
handler: (c: AppContext) => {
|
|
277
|
+
const db = c.db
|
|
278
|
+
if (!db) return c.json({ error: 'No database' }, 500)
|
|
279
|
+
|
|
280
|
+
// Use database connection
|
|
281
|
+
return c.json({ users: [] })
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
]
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
await app.start()
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
- ### 9. Response Methods
|
|
291
|
+
|
|
292
|
+
```typescript
|
|
293
|
+
import { server, type AppContext } from '@minejs/server'
|
|
294
|
+
|
|
295
|
+
const app = server({
|
|
296
|
+
port: 3000,
|
|
297
|
+
routes: [
|
|
298
|
+
{
|
|
299
|
+
method: 'GET',
|
|
300
|
+
path: '/json',
|
|
301
|
+
handler: (c: AppContext) => c.json({ message: 'Hello' })
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
method: 'GET',
|
|
305
|
+
path: '/text',
|
|
306
|
+
handler: (c: AppContext) => c.text('Plain text response')
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
method: 'GET',
|
|
310
|
+
path: '/html',
|
|
311
|
+
handler: (c: AppContext) => c.html('<h1>HTML Page</h1>')
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
method: 'GET',
|
|
315
|
+
path: '/file',
|
|
316
|
+
handler: (c: AppContext) => c.file('./public/document.pdf', 'application/pdf')
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
method: 'GET',
|
|
320
|
+
path: '/redirect',
|
|
321
|
+
handler: (c: AppContext) => c.redirect('/new-location', 301)
|
|
322
|
+
}
|
|
323
|
+
]
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
await app.start()
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
- ### 10. Lifecycle Hooks
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
import { server, type ServerInstance } from '@minejs/server'
|
|
333
|
+
|
|
334
|
+
const app = server({
|
|
335
|
+
port: 3000,
|
|
336
|
+
logging: true,
|
|
337
|
+
|
|
338
|
+
onStartup: async (app) => {
|
|
339
|
+
console.log('Server starting up...')
|
|
340
|
+
},
|
|
341
|
+
|
|
342
|
+
onReady: async (app, databases) => {
|
|
343
|
+
console.log('Server is ready!')
|
|
344
|
+
console.log('Database connections:', databases.size)
|
|
345
|
+
},
|
|
346
|
+
|
|
347
|
+
onShutdown: async () => {
|
|
348
|
+
console.log('Server shutting down...')
|
|
349
|
+
},
|
|
350
|
+
|
|
351
|
+
onError: async (statusCode, path, method) => {
|
|
352
|
+
return new Response(
|
|
353
|
+
JSON.stringify({
|
|
354
|
+
error: 'Custom error page',
|
|
355
|
+
statusCode,
|
|
356
|
+
path,
|
|
357
|
+
method
|
|
358
|
+
}),
|
|
359
|
+
{ status: statusCode, headers: { 'Content-Type': 'application/json' } }
|
|
360
|
+
)
|
|
361
|
+
}
|
|
362
|
+
})
|
|
363
|
+
|
|
364
|
+
await app.start()
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
- ### 11. Internationalization (i18n)
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
import { server, type AppContext } from '@minejs/server'
|
|
371
|
+
|
|
372
|
+
const app = server({
|
|
373
|
+
port: 3000,
|
|
374
|
+
i18n: {
|
|
375
|
+
defaultLanguage: 'en',
|
|
376
|
+
supportedLanguages: ['en', 'ar', 'fr'],
|
|
377
|
+
staticPath: './src/i18n'
|
|
378
|
+
},
|
|
379
|
+
routes: [
|
|
380
|
+
{
|
|
381
|
+
method: 'GET',
|
|
382
|
+
path: '/greeting',
|
|
383
|
+
handler: (c: AppContext) => {
|
|
384
|
+
const language = c.lang // Detected from query, cookie, or header
|
|
385
|
+
return c.json({
|
|
386
|
+
language,
|
|
387
|
+
greeting: 'Hello'
|
|
388
|
+
})
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
]
|
|
392
|
+
})
|
|
393
|
+
|
|
394
|
+
await app.start()
|
|
395
|
+
// Language detection priority: ?lang query param > lang cookie > Accept-Language header > default
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
- ### 12. Logging
|
|
399
|
+
|
|
400
|
+
```typescript
|
|
401
|
+
import { server } from '@minejs/server'
|
|
402
|
+
|
|
403
|
+
const app = server({
|
|
404
|
+
port: 3000,
|
|
405
|
+
logging: {
|
|
406
|
+
level: 'info', // 'debug' | 'info' | 'warn' | 'error'
|
|
407
|
+
pretty: true // Pretty-print logs with colors
|
|
408
|
+
}
|
|
409
|
+
})
|
|
410
|
+
|
|
411
|
+
await app.start()
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
<br>
|
|
415
|
+
|
|
416
|
+
- ## API Reference 🔥
|
|
417
|
+
|
|
418
|
+
- #### `server(config?: ServerConfig): ServerInstance`
|
|
419
|
+
> Create and configure an HTTP server instance.
|
|
420
|
+
|
|
421
|
+
```typescript
|
|
422
|
+
const app = server({
|
|
423
|
+
port: 3000,
|
|
424
|
+
hostname: 'localhost',
|
|
425
|
+
logging: true,
|
|
426
|
+
routes: [],
|
|
427
|
+
security: {},
|
|
428
|
+
database: {},
|
|
429
|
+
static: {}
|
|
430
|
+
})
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
**Configuration Options:**
|
|
434
|
+
- `port` (number | string): Server port, default: `3000`
|
|
435
|
+
- `hostname` (string): Server hostname, default: `localhost`
|
|
436
|
+
- `requestTimeout` (number): Request timeout in ms, default: `30000`
|
|
437
|
+
- `maxRequestSize` (number): Max request body size, default: `10MB`
|
|
438
|
+
- `gracefulShutdownTimeout` (number): Shutdown timeout in ms, default: `10000`
|
|
439
|
+
- `logging` (boolean | LoggingConfig): Enable logging
|
|
440
|
+
- `security` (boolean | SecurityConfig): Security settings
|
|
441
|
+
- `database` (DatabaseConfig | DatabaseConfig[]): Database connections
|
|
442
|
+
- `i18n` (boolean | I18nConfig): Internationalization settings
|
|
443
|
+
- `static` (StaticConfig | StaticConfig[]): Static file serving
|
|
444
|
+
- `routes` (RouteDefinition[]): Route definitions
|
|
445
|
+
- `middlewares` (AppMiddleware[]): Global middlewares
|
|
446
|
+
- `onStartup` (fn): Called on startup
|
|
447
|
+
- `onReady` (fn): Called when server is ready
|
|
448
|
+
- `onShutdown` (fn): Called on shutdown
|
|
449
|
+
- `onError` (fn): Custom error page handler
|
|
450
|
+
|
|
451
|
+
- #### `ServerInstance`
|
|
452
|
+
|
|
453
|
+
```typescript
|
|
454
|
+
interface ServerInstance {
|
|
455
|
+
app: unknown // Underlying Bun server
|
|
456
|
+
logger: Logger | null // Logger instance
|
|
457
|
+
db: Map<string, unknown> // Database connections
|
|
458
|
+
bunServer: unknown // Bun server object
|
|
459
|
+
start(): Promise<void> // Start the server
|
|
460
|
+
stop(): Promise<void> // Stop the server
|
|
461
|
+
addRoute(route: RouteDefinition): void
|
|
462
|
+
addRoutes(routes: RouteDefinition[]): void
|
|
463
|
+
getRoutes(): RouteDefinition[]
|
|
464
|
+
}
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
**Methods:**
|
|
468
|
+
```typescript
|
|
469
|
+
// Start server
|
|
470
|
+
await app.start()
|
|
471
|
+
|
|
472
|
+
// Stop server gracefully
|
|
473
|
+
await app.stop()
|
|
474
|
+
|
|
475
|
+
// Add route dynamically
|
|
476
|
+
app.addRoute({
|
|
477
|
+
method: 'GET',
|
|
478
|
+
path: '/dynamic',
|
|
479
|
+
handler: (c) => c.json({ dynamic: true })
|
|
480
|
+
})
|
|
481
|
+
|
|
482
|
+
// Add multiple routes
|
|
483
|
+
app.addRoutes([
|
|
484
|
+
{ method: 'GET', path: '/route1', handler: (c) => c.json({}) },
|
|
485
|
+
{ method: 'POST', path: '/route2', handler: (c) => c.json({}) }
|
|
486
|
+
])
|
|
487
|
+
|
|
488
|
+
// Get all routes
|
|
489
|
+
const routes = app.getRoutes()
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
- #### `AppContext`
|
|
493
|
+
|
|
494
|
+
> Request context passed to every route handler and middleware.
|
|
495
|
+
|
|
496
|
+
```typescript
|
|
497
|
+
interface AppContext {
|
|
498
|
+
// Request info
|
|
499
|
+
ip: string // Client IP address
|
|
500
|
+
request: Request // Fetch API Request object
|
|
501
|
+
params: Record<string, string> // Route parameters
|
|
502
|
+
query: Record<string, string> // Query string parameters
|
|
503
|
+
body: unknown // Parsed request body
|
|
504
|
+
headers: Headers // Request headers
|
|
505
|
+
|
|
506
|
+
// Server info
|
|
507
|
+
db: DB | undefined // Database connection
|
|
508
|
+
logger: Logger | null // Logger instance
|
|
509
|
+
i18n: I18nManager | null // i18n manager
|
|
510
|
+
lang: string // Current language
|
|
511
|
+
user?: unknown // User (from middleware)
|
|
512
|
+
requestId: string // Unique request ID
|
|
513
|
+
state: Record<string, unknown> // Custom state
|
|
514
|
+
|
|
515
|
+
// Response methods
|
|
516
|
+
json(data: unknown, status?: number): Response
|
|
517
|
+
text(data: string, status?: number): Response
|
|
518
|
+
html(data: string, status?: number): Response
|
|
519
|
+
redirect(url: string, status?: number): Response
|
|
520
|
+
file(path: string, contentType?: string): Response
|
|
521
|
+
|
|
522
|
+
// Cookie methods
|
|
523
|
+
setCookie(name: string, value: string, options?: CookieOptions): AppContext
|
|
524
|
+
getCookie(name: string): string | undefined
|
|
525
|
+
deleteCookie(name: string, options?: CookieOptions): AppContext
|
|
526
|
+
|
|
527
|
+
// Header methods
|
|
528
|
+
setHeader(key: string, value: string): AppContext
|
|
529
|
+
getHeader(key: string): string | undefined
|
|
530
|
+
|
|
531
|
+
// Status code
|
|
532
|
+
status(code: number): AppContext
|
|
533
|
+
statusCode: number
|
|
534
|
+
}
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
- #### `RouteDefinition`
|
|
538
|
+
|
|
539
|
+
```typescript
|
|
540
|
+
interface RouteDefinition {
|
|
541
|
+
method: HttpMethod | HttpMethod[] // 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'OPTIONS' | 'HEAD'
|
|
542
|
+
path: string // Route path: '/users' or '/users/:id' or '/files/*'
|
|
543
|
+
handler: RouteHandler // Route handler function
|
|
544
|
+
middlewares?: AppMiddleware[] // Route-specific middlewares
|
|
545
|
+
validate?: ValidationSchema // Input validation schema
|
|
546
|
+
timeout?: number // Route-specific timeout
|
|
547
|
+
rateLimit?: RateLimitConfig // Route-specific rate limiting
|
|
548
|
+
cache?: number // Response cache duration in seconds
|
|
549
|
+
tags?: string[] // Route tags for documentation
|
|
550
|
+
}
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
**Examples:**
|
|
554
|
+
```typescript
|
|
555
|
+
// Static route
|
|
556
|
+
{ method: 'GET', path: '/hello', handler: (c) => c.json({}) }
|
|
557
|
+
|
|
558
|
+
// Dynamic route with parameter
|
|
559
|
+
{ method: 'GET', path: '/users/:id', handler: (c) => c.json({ id: c.params.id }) }
|
|
560
|
+
|
|
561
|
+
// Wildcard route
|
|
562
|
+
{ method: 'GET', path: '/files/*', handler: (c) => c.json({}) }
|
|
563
|
+
|
|
564
|
+
// Multiple methods
|
|
565
|
+
{ method: ['GET', 'POST'], path: '/data', handler: (c) => c.json({}) }
|
|
566
|
+
|
|
567
|
+
// With middlewares
|
|
568
|
+
{
|
|
569
|
+
method: 'POST',
|
|
570
|
+
path: '/admin',
|
|
571
|
+
handler: (c) => c.json({ admin: true }),
|
|
572
|
+
middlewares: [authMiddleware, adminMiddleware]
|
|
573
|
+
}
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
- #### `CookieOptions`
|
|
577
|
+
|
|
578
|
+
```typescript
|
|
579
|
+
interface CookieOptions {
|
|
580
|
+
maxAge?: number // Cookie lifetime in seconds
|
|
581
|
+
expires?: Date // Cookie expiration date
|
|
582
|
+
path?: string // Cookie path (default: '/')
|
|
583
|
+
domain?: string // Cookie domain
|
|
584
|
+
secure?: boolean // HTTPS only
|
|
585
|
+
httpOnly?: boolean // JavaScript cannot access
|
|
586
|
+
sameSite?: 'Strict' | 'Lax' | 'None' // CSRF protection
|
|
587
|
+
}
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
- #### `SecurityConfig`
|
|
591
|
+
|
|
592
|
+
```typescript
|
|
593
|
+
interface SecurityConfig {
|
|
594
|
+
cors?: CorsConfig | boolean
|
|
595
|
+
rateLimit?: RateLimitConfig | boolean
|
|
596
|
+
csrf?: CsrfConfig | boolean
|
|
597
|
+
helmet?: HelmetConfig | boolean
|
|
598
|
+
auth?: AuthConfig | boolean
|
|
599
|
+
}
|
|
600
|
+
```
|
|
601
|
+
|
|
602
|
+
**CORS:**
|
|
603
|
+
```typescript
|
|
604
|
+
security: {
|
|
605
|
+
cors: {
|
|
606
|
+
origin: '*' | string | string[] | (origin: string) => boolean
|
|
607
|
+
methods: ['GET', 'POST', 'PUT', 'DELETE']
|
|
608
|
+
allowedHeaders: ['Content-Type', 'Authorization']
|
|
609
|
+
credentials: true
|
|
610
|
+
maxAge: 3600
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
**Rate Limiting:**
|
|
616
|
+
```typescript
|
|
617
|
+
security: {
|
|
618
|
+
rateLimit: {
|
|
619
|
+
windowMs: 60000 // Time window in ms
|
|
620
|
+
max: 100 // Max requests per window
|
|
621
|
+
keyGenerator: (c) => c.ip // Custom key generator
|
|
622
|
+
message: 'Too many requests'
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
- #### `LoggingConfig`
|
|
628
|
+
|
|
629
|
+
```typescript
|
|
630
|
+
interface LoggingConfig {
|
|
631
|
+
level?: 'debug' | 'info' | 'warn' | 'error'
|
|
632
|
+
pretty?: boolean
|
|
633
|
+
}
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
- #### `DatabaseConfig`
|
|
637
|
+
|
|
638
|
+
```typescript
|
|
639
|
+
interface DatabaseConfig {
|
|
640
|
+
name?: string // Database name, default: 'default'
|
|
641
|
+
connection: string // Connection string or file path
|
|
642
|
+
timeout?: number // Connection timeout
|
|
643
|
+
}
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
- #### `StaticConfig`
|
|
647
|
+
|
|
648
|
+
```typescript
|
|
649
|
+
interface StaticConfig {
|
|
650
|
+
path: string // URL prefix (e.g., '/public')
|
|
651
|
+
directory: string // Local directory path
|
|
652
|
+
maxAge?: number // Cache duration in seconds
|
|
653
|
+
index?: string[] // Index files
|
|
654
|
+
dotfiles?: 'allow' | 'deny' | 'ignore'
|
|
655
|
+
etag?: boolean // Enable ETag headers
|
|
656
|
+
lastModified?: boolean // Enable Last-Modified headers
|
|
657
|
+
immutable?: boolean // Add immutable cache directive
|
|
658
|
+
extensions?: string[] // Try extensions
|
|
659
|
+
fallthrough?: boolean // Continue if file not found
|
|
660
|
+
setHeaders?: (ctx: AppContext, path: string) => void
|
|
661
|
+
}
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
- #### `Error Classes`
|
|
665
|
+
|
|
666
|
+
```typescript
|
|
667
|
+
class AppError extends Error {
|
|
668
|
+
statusCode: number
|
|
669
|
+
code?: string
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
class ValidationError extends AppError {
|
|
673
|
+
issues?: unknown
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
class DatabaseError extends AppError {}
|
|
677
|
+
class TimeoutError extends AppError {}
|
|
678
|
+
class RateLimitError extends AppError {}
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
**Usage:**
|
|
682
|
+
```typescript
|
|
683
|
+
import { server, AppError, ValidationError } from '@minejs/server'
|
|
684
|
+
|
|
685
|
+
const app = server({
|
|
686
|
+
port: 3000,
|
|
687
|
+
routes: [
|
|
688
|
+
{
|
|
689
|
+
method: 'GET',
|
|
690
|
+
path: '/error',
|
|
691
|
+
handler: (c) => {
|
|
692
|
+
throw new AppError('Something went wrong', 500, 'INTERNAL_ERROR')
|
|
693
|
+
}
|
|
694
|
+
},
|
|
695
|
+
{
|
|
696
|
+
method: 'POST',
|
|
697
|
+
path: '/validate',
|
|
698
|
+
handler: (c) => {
|
|
699
|
+
if (!c.body.email) {
|
|
700
|
+
throw new ValidationError('Email is required')
|
|
701
|
+
}
|
|
702
|
+
return c.json({ valid: true })
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
]
|
|
706
|
+
})
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
<br>
|
|
710
|
+
|
|
711
|
+
- ## Advanced Features 🚀
|
|
712
|
+
|
|
713
|
+
- ### Middleware Chain
|
|
714
|
+
|
|
715
|
+
Middlewares execute in order and can short-circuit the chain:
|
|
716
|
+
|
|
717
|
+
```typescript
|
|
718
|
+
const authMiddleware: AppMiddleware = async (c, next) => {
|
|
719
|
+
const token = c.getHeader('Authorization')
|
|
720
|
+
if (!token) return c.json({ error: 'Unauthorized' }, 401)
|
|
721
|
+
await next() // Continue to next middleware/handler
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
const loggingMiddleware: AppMiddleware = async (c, next) => {
|
|
725
|
+
console.log('Before:', c.request.method, c.request.url)
|
|
726
|
+
await next()
|
|
727
|
+
console.log('After:', c.statusCode)
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
app.addRoute({
|
|
731
|
+
method: 'GET',
|
|
732
|
+
path: '/protected',
|
|
733
|
+
handler: (c) => c.json({ data: 'secret' }),
|
|
734
|
+
middlewares: [loggingMiddleware, authMiddleware]
|
|
735
|
+
})
|
|
736
|
+
```
|
|
737
|
+
|
|
738
|
+
- ### Multiple Databases
|
|
739
|
+
|
|
740
|
+
```typescript
|
|
741
|
+
const app = server({
|
|
742
|
+
port: 3000,
|
|
743
|
+
database: [
|
|
744
|
+
{ name: 'primary', connection: './primary.db' },
|
|
745
|
+
{ name: 'cache', connection: './cache.db' },
|
|
746
|
+
{ name: 'logs', connection: ':memory:' }
|
|
747
|
+
]
|
|
748
|
+
})
|
|
749
|
+
|
|
750
|
+
await app.start()
|
|
751
|
+
|
|
752
|
+
// Access in routes
|
|
753
|
+
const route = {
|
|
754
|
+
method: 'GET',
|
|
755
|
+
path: '/data',
|
|
756
|
+
handler: (c: AppContext) => {
|
|
757
|
+
const primary = app.db.get('primary')
|
|
758
|
+
const cache = app.db.get('cache')
|
|
759
|
+
return c.json({ primary, cache })
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
```
|
|
763
|
+
|
|
764
|
+
- ### Dynamic Routes
|
|
765
|
+
|
|
766
|
+
```typescript
|
|
767
|
+
const app = server({
|
|
768
|
+
port: 3000,
|
|
769
|
+
logging: false,
|
|
770
|
+
routes: [
|
|
771
|
+
{
|
|
772
|
+
method: 'GET',
|
|
773
|
+
path: '/api/users/:id',
|
|
774
|
+
handler: (c) => c.json({ userId: c.params.id })
|
|
775
|
+
},
|
|
776
|
+
{
|
|
777
|
+
method: 'GET',
|
|
778
|
+
path: '/download/*',
|
|
779
|
+
handler: (c) => c.file('./downloads/' + c.params[0])
|
|
780
|
+
}
|
|
781
|
+
]
|
|
782
|
+
})
|
|
783
|
+
|
|
784
|
+
await app.start()
|
|
785
|
+
```
|
|
786
|
+
|
|
787
|
+
- ### Error Handling
|
|
788
|
+
|
|
789
|
+
```typescript
|
|
790
|
+
const app = server({
|
|
791
|
+
port: 3000,
|
|
792
|
+
errorHandler: async (error, context) => {
|
|
793
|
+
// Custom error handling
|
|
794
|
+
console.error('Error:', error.message)
|
|
795
|
+
},
|
|
796
|
+
onError: async (statusCode, path, method) => {
|
|
797
|
+
// Custom error pages
|
|
798
|
+
if (statusCode === 404) {
|
|
799
|
+
return new Response(
|
|
800
|
+
JSON.stringify({ error: 'Page not found', path }),
|
|
801
|
+
{ status: 404, headers: { 'Content-Type': 'application/json' } }
|
|
802
|
+
)
|
|
803
|
+
}
|
|
804
|
+
return new Response('Error', { status: statusCode })
|
|
805
|
+
}
|
|
806
|
+
})
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
- ### Health & Readiness Endpoints
|
|
810
|
+
|
|
811
|
+
Built-in endpoints for monitoring:
|
|
812
|
+
|
|
813
|
+
```typescript
|
|
814
|
+
// GET /health - Server health status
|
|
815
|
+
// Response: { status: 'healthy', uptime: 123.45, activeRequests: 5, ... }
|
|
816
|
+
|
|
817
|
+
// GET /readiness - Server readiness status
|
|
818
|
+
// Response: { ready: true, checks: { database: 'connected', ... }, ... }
|
|
819
|
+
```
|
|
820
|
+
|
|
821
|
+
- ### Request Lifecycle
|
|
822
|
+
|
|
823
|
+
1. **Receive** - Request received by server
|
|
824
|
+
2. **Parse** - URL, method, headers, body parsed
|
|
825
|
+
3. **Security** - CORS, rate limiting, validation
|
|
826
|
+
4. **Match** - Route matching from router
|
|
827
|
+
5. **Context** - AppContext created
|
|
828
|
+
6. **Middlewares** - Route middlewares execute in order
|
|
829
|
+
7. **Handler** - Route handler executes
|
|
830
|
+
8. **Response** - Response sent with security headers
|
|
831
|
+
9. **Log** - Request logged with duration
|
|
832
|
+
|
|
833
|
+
<br>
|
|
834
|
+
|
|
835
|
+
<!-- ╚══════════════════════════════════════════════════════════════════╝ -->
|
|
836
|
+
|
|
837
|
+
|
|
838
|
+
|
|
839
|
+
<!-- ╔══════════════════════════════ END ══════════════════════════════╗ -->
|
|
840
|
+
|
|
841
|
+
<br>
|
|
842
|
+
|
|
843
|
+
---
|
|
844
|
+
|
|
845
|
+
<div align="center">
|
|
846
|
+
<a href="https://github.com/maysara-elshewehy"><img src="https://img.shields.io/badge/by-Maysara-black"/></a>
|
|
847
|
+
</div>
|
|
848
|
+
|
|
849
|
+
<!-- ╚═════════════════════════════════════════════════════════════════╝ -->
|