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