@je-es/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 +802 -0
- package/dist/main.cjs +11 -0
- package/dist/main.cjs.map +1 -0
- package/dist/main.d.cts +262 -0
- package/dist/main.d.ts +262 -0
- package/dist/main.js +11 -0
- package/dist/main.js.map +1 -0
- package/package.json +59 -0
package/README.md
ADDED
|
@@ -0,0 +1,802 @@
|
|
|
1
|
+
<!-- ╔══════════════════════════════ BEG ══════════════════════════════╗ -->
|
|
2
|
+
|
|
3
|
+
<br>
|
|
4
|
+
<div align="center">
|
|
5
|
+
<p>
|
|
6
|
+
<img src="./assets/img/logo.png" alt="logo" style="" height="70" />
|
|
7
|
+
</p>
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<div align="center">
|
|
11
|
+
<img src="https://img.shields.io/badge/v-0.0.1-black"/>
|
|
12
|
+
<a href="https://github.com/maysara-elshewehy">
|
|
13
|
+
</a>
|
|
14
|
+
<a href="https://github.com/je-es/server"> <img src="https://img.shields.io/badge/🔥-@je--es/server-black"/> </a>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<div align="center">
|
|
18
|
+
<img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/>
|
|
19
|
+
<br>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<!-- ╚═════════════════════════════════════════════════════════════════╝ -->
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
<!-- ╔══════════════════════════════ DOC ══════════════════════════════╗ -->
|
|
27
|
+
|
|
28
|
+
- ## Quick Start 🔥
|
|
29
|
+
|
|
30
|
+
- ### commands
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# install
|
|
34
|
+
space i @je-es/server
|
|
35
|
+
|
|
36
|
+
# manage
|
|
37
|
+
space test # run server tests
|
|
38
|
+
space build # build server files to ./dist
|
|
39
|
+
space start # start server
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
- ### Basic Server
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
// import
|
|
46
|
+
import { server } from '@je-es/server';
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
// create
|
|
51
|
+
const app = server({
|
|
52
|
+
port : 3000,
|
|
53
|
+
routes : [
|
|
54
|
+
{
|
|
55
|
+
method : 'GET',
|
|
56
|
+
path : '/',
|
|
57
|
+
handler : (c) => c.json({ message: 'Hello World!' })
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
method : 'GET',
|
|
61
|
+
path : '/users/:id',
|
|
62
|
+
handler : (c) => {
|
|
63
|
+
const userId = c.params.id;
|
|
64
|
+
return c.json({ userId, name: 'John Doe' });
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
method : 'POST',
|
|
69
|
+
path : '/users',
|
|
70
|
+
handler : (c) => {
|
|
71
|
+
const userData = c.body;
|
|
72
|
+
return c.json({ created: true, data: userData });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
// start
|
|
81
|
+
await app.start();
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
> use `space start` to run your server.
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
> space start
|
|
88
|
+
|
|
89
|
+
→ URL: http://localhost:3000
|
|
90
|
+
→ Environment: test
|
|
91
|
+
→ Routes: N
|
|
92
|
+
...
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
- ### With Database
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
import { server, sqliteTable, integer, text } from '@je-es/server';
|
|
99
|
+
|
|
100
|
+
// Define your schema - all Drizzle types exported from @je-es/server!
|
|
101
|
+
const users = sqliteTable('users', {
|
|
102
|
+
id : integer('id').primaryKey(),
|
|
103
|
+
name : text('name').notNull(),
|
|
104
|
+
email : text('email').notNull()
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const app = server({
|
|
108
|
+
port : 3000,
|
|
109
|
+
database: {
|
|
110
|
+
connection : './my_app.db', // File-based SQLite database
|
|
111
|
+
schema : { users }
|
|
112
|
+
},
|
|
113
|
+
routes : [
|
|
114
|
+
{
|
|
115
|
+
method : 'GET',
|
|
116
|
+
path : '/users',
|
|
117
|
+
handler : async (c) => {
|
|
118
|
+
const allUsers = await c.db.select().from(users);
|
|
119
|
+
return c.json(allUsers);
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
method : 'POST',
|
|
124
|
+
path : '/users',
|
|
125
|
+
handler : async (c) => {
|
|
126
|
+
const newUser = await c.db
|
|
127
|
+
.insert(users)
|
|
128
|
+
.values(c.body)
|
|
129
|
+
.returning();
|
|
130
|
+
return c.json(newUser[0]);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
]
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
await app.start();
|
|
137
|
+
// Data persists in ./my_app.db file!
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
- ### With Security
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
import { server } from '@je-es/server';
|
|
144
|
+
|
|
145
|
+
const app = server({
|
|
146
|
+
port : 3000,
|
|
147
|
+
security: {
|
|
148
|
+
// Rate limiting
|
|
149
|
+
rateLimit: {
|
|
150
|
+
max : 100,
|
|
151
|
+
windowMs : 60000, // 1 minute
|
|
152
|
+
message : 'Too many requests, please try again later'
|
|
153
|
+
},
|
|
154
|
+
// CORS configuration
|
|
155
|
+
cors: {
|
|
156
|
+
origin : ['http://localhost:3000', 'https://example.com'],
|
|
157
|
+
credentials : true,
|
|
158
|
+
methods : ['GET', 'POST', 'PUT', 'DELETE'],
|
|
159
|
+
allowedHeaders : ['Content-Type', 'Authorization']
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
routes : [
|
|
163
|
+
{
|
|
164
|
+
method : 'GET',
|
|
165
|
+
path : '/protected',
|
|
166
|
+
handler : (c) => c.json({ message: 'This route is protected!' })
|
|
167
|
+
}
|
|
168
|
+
]
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
await app.start();
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
<div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
|
|
175
|
+
|
|
176
|
+
- ## API Reference
|
|
177
|
+
|
|
178
|
+
- ### Server Configuration
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
import { server, type ServerConfig } from '@je-es/server';
|
|
182
|
+
|
|
183
|
+
const config: ServerConfig = {
|
|
184
|
+
// Basic settings
|
|
185
|
+
port : 3000,
|
|
186
|
+
hostname : 'localhost',
|
|
187
|
+
|
|
188
|
+
// Request handling
|
|
189
|
+
requestTimeout : 30000, // 30 seconds
|
|
190
|
+
maxRequestSize : 10485760, // 10MB
|
|
191
|
+
gracefulShutdownTimeout : 10000, // 10 seconds
|
|
192
|
+
|
|
193
|
+
// Logging
|
|
194
|
+
logging: {
|
|
195
|
+
level : 'info', // 'debug' | 'info' | 'warn' | 'error'
|
|
196
|
+
pretty : false // Enable pretty printing
|
|
197
|
+
},
|
|
198
|
+
|
|
199
|
+
// Database
|
|
200
|
+
database: {
|
|
201
|
+
type : 'bun-sql',
|
|
202
|
+
connection : ':memory:',
|
|
203
|
+
schema : {}
|
|
204
|
+
},
|
|
205
|
+
|
|
206
|
+
// Security
|
|
207
|
+
security: {
|
|
208
|
+
rateLimit : true,
|
|
209
|
+
cors : true,
|
|
210
|
+
csrf : true
|
|
211
|
+
},
|
|
212
|
+
|
|
213
|
+
// Routes
|
|
214
|
+
routes: [],
|
|
215
|
+
|
|
216
|
+
// Lifecycle hooks
|
|
217
|
+
onShutdown: async () => {
|
|
218
|
+
console.log('Server shutting down...');
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const app = server(config);
|
|
223
|
+
await app.start();
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
- ### Route Definition
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
import { type RouteDefinition, type AppContext } from '@je-es/server';
|
|
230
|
+
|
|
231
|
+
// Single HTTP method
|
|
232
|
+
const route: RouteDefinition = {
|
|
233
|
+
method : 'GET',
|
|
234
|
+
path : '/users/:id',
|
|
235
|
+
handler : (c: AppContext) => {
|
|
236
|
+
return c.json({ id: c.params.id });
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
// Multiple HTTP methods
|
|
241
|
+
const multiMethodRoute: RouteDefinition = {
|
|
242
|
+
method : ['GET', 'POST'],
|
|
243
|
+
path : '/api/resource',
|
|
244
|
+
handler : (c: AppContext) => {
|
|
245
|
+
if (c.request.method === 'GET') {
|
|
246
|
+
return c.json({ method : 'GET' });
|
|
247
|
+
}
|
|
248
|
+
return c.json({ method : 'POST', body: c.body });
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
// Dynamic routes with nested parameters
|
|
253
|
+
const nestedRoute: RouteDefinition = {
|
|
254
|
+
method : 'GET',
|
|
255
|
+
path : '/posts/:postId/comments/:commentId',
|
|
256
|
+
handler : (c: AppContext) => {
|
|
257
|
+
return c.json({
|
|
258
|
+
postId: c.params.postId,
|
|
259
|
+
commentId: c.params.commentId
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
- ### Context API
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
import { type AppContext } from '@je-es/server';
|
|
269
|
+
|
|
270
|
+
// Response methods
|
|
271
|
+
handler : (c: AppContext) => {
|
|
272
|
+
// JSON response
|
|
273
|
+
c.json({ data: 'value' }, 200);
|
|
274
|
+
|
|
275
|
+
// Text response
|
|
276
|
+
c.text('Hello World', 200);
|
|
277
|
+
|
|
278
|
+
// HTML response
|
|
279
|
+
c.html('<h1>Hello</h1>', 200);
|
|
280
|
+
|
|
281
|
+
// Redirect
|
|
282
|
+
c.redirect('/new-location', 302);
|
|
283
|
+
|
|
284
|
+
// File response
|
|
285
|
+
c.file('./path/to/file.pdf', 'application/pdf');
|
|
286
|
+
|
|
287
|
+
// Chain status
|
|
288
|
+
c.status(201).json({ created: true });
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Request data
|
|
292
|
+
handler : (c: AppContext) => {
|
|
293
|
+
const params = c.params; // URL parameters
|
|
294
|
+
const query = c.query; // Query string
|
|
295
|
+
const body = c.body; // Request body
|
|
296
|
+
const headers = c.headers; // Request headers
|
|
297
|
+
const db = c.db; // Database instance
|
|
298
|
+
const logger = c.logger; // Logger instance
|
|
299
|
+
const requestId = c.requestId; // Unique request ID
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Headers
|
|
303
|
+
handler : (c: AppContext) => {
|
|
304
|
+
c.setHeader('X-Custom-Header', 'value');
|
|
305
|
+
const auth = c.getHeader('Authorization');
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Cookies
|
|
309
|
+
handler : (c: AppContext) => {
|
|
310
|
+
c.setCookie('session', 'abc123', {
|
|
311
|
+
maxAge: 3600,
|
|
312
|
+
httpOnly: true,
|
|
313
|
+
secure: true,
|
|
314
|
+
sameSite: 'Strict'
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
const session = c.getCookie('session');
|
|
318
|
+
c.deleteCookie('session');
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
<div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
|
|
323
|
+
|
|
324
|
+
- ## Security Features
|
|
325
|
+
|
|
326
|
+
- ### Rate Limiting
|
|
327
|
+
|
|
328
|
+
```typescript
|
|
329
|
+
const app = server({
|
|
330
|
+
security: {
|
|
331
|
+
rateLimit: {
|
|
332
|
+
max: 100, // Max requests per window
|
|
333
|
+
windowMs: 60000, // Time window in milliseconds
|
|
334
|
+
keyGenerator: (c) => {
|
|
335
|
+
// Custom key generation (default: IP address)
|
|
336
|
+
return c.headers.get('x-api-key') || c.request.ip;
|
|
337
|
+
},
|
|
338
|
+
message: 'Rate limit exceeded'
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
- ### CORS Configuration
|
|
345
|
+
|
|
346
|
+
```typescript
|
|
347
|
+
const app = server({
|
|
348
|
+
security: {
|
|
349
|
+
cors: {
|
|
350
|
+
// Allow specific origins
|
|
351
|
+
origin: ['http://localhost:3000', 'https://example.com'],
|
|
352
|
+
|
|
353
|
+
// Or use a function
|
|
354
|
+
origin: (origin) => {
|
|
355
|
+
return origin.endsWith('.example.com');
|
|
356
|
+
},
|
|
357
|
+
|
|
358
|
+
// Or allow all
|
|
359
|
+
origin: '*',
|
|
360
|
+
|
|
361
|
+
methods: ['GET', 'POST', 'PUT', 'DELETE'],
|
|
362
|
+
allowedHeaders: ['Content-Type', 'Authorization'],
|
|
363
|
+
credentials: true,
|
|
364
|
+
maxAge: 86400 // 24 hours
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
- ### CSRF Protection
|
|
371
|
+
|
|
372
|
+
```typescript
|
|
373
|
+
import { SecurityManager } from '@je-es/server';
|
|
374
|
+
|
|
375
|
+
const security = new SecurityManager();
|
|
376
|
+
|
|
377
|
+
// Generate CSRF token
|
|
378
|
+
const token = security.generateCsrfToken('session-id', 3600000); // 1 hour TTL
|
|
379
|
+
|
|
380
|
+
// Validate CSRF token
|
|
381
|
+
const isValid = security.validateCsrfToken(token, 'session-id');
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
- ### Input Sanitization
|
|
385
|
+
|
|
386
|
+
```typescript
|
|
387
|
+
import { SecurityManager } from '@je-es/server';
|
|
388
|
+
|
|
389
|
+
const security = new SecurityManager();
|
|
390
|
+
|
|
391
|
+
// Sanitize HTML
|
|
392
|
+
const cleanHtml = security.sanitizeHtml('<script>alert("xss")</script>');
|
|
393
|
+
// Output: <script>alert("xss")</script>
|
|
394
|
+
|
|
395
|
+
// Sanitize SQL
|
|
396
|
+
const cleanSql = security.sanitizeSql("'; DROP TABLE users--");
|
|
397
|
+
// Output: ''; DROP TABLE users--
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
<div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
|
|
401
|
+
|
|
402
|
+
- ## Database Support
|
|
403
|
+
|
|
404
|
+
- ### Single Database
|
|
405
|
+
|
|
406
|
+
```typescript
|
|
407
|
+
import { server } from '@je-es/server';
|
|
408
|
+
|
|
409
|
+
const app = server({
|
|
410
|
+
database: {
|
|
411
|
+
connection: './my_app.db' // ✅ File-based SQLite - data persists!
|
|
412
|
+
// or ':memory:' for in-memory database
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
// Access in routes via c.db
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
- ### Multiple Databases
|
|
420
|
+
|
|
421
|
+
```typescript
|
|
422
|
+
import { server } from '@je-es/server';
|
|
423
|
+
|
|
424
|
+
const app = server({
|
|
425
|
+
database: [
|
|
426
|
+
{
|
|
427
|
+
name: 'default',
|
|
428
|
+
connection: './main.db' // Main database file
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
name: 'analytics',
|
|
432
|
+
connection: './analytics.db' // Analytics database file
|
|
433
|
+
}
|
|
434
|
+
],
|
|
435
|
+
routes : [
|
|
436
|
+
{
|
|
437
|
+
method : 'GET',
|
|
438
|
+
path : '/data',
|
|
439
|
+
handler : async (c) => {
|
|
440
|
+
// Access default database
|
|
441
|
+
const users = await c.db.select().from(usersTable);
|
|
442
|
+
|
|
443
|
+
// Access named databases
|
|
444
|
+
const mainDb = app.db.get('default');
|
|
445
|
+
const analyticsDb = app.db.get('analytics');
|
|
446
|
+
|
|
447
|
+
return c.json({ users });
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
]
|
|
451
|
+
});
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
- ### Complete Database Example
|
|
455
|
+
|
|
456
|
+
```typescript
|
|
457
|
+
import {
|
|
458
|
+
server,
|
|
459
|
+
sqliteTable,
|
|
460
|
+
integer,
|
|
461
|
+
text,
|
|
462
|
+
real,
|
|
463
|
+
eq,
|
|
464
|
+
and,
|
|
465
|
+
like
|
|
466
|
+
} from '@je-es/server';
|
|
467
|
+
|
|
468
|
+
// Define schema with all column types
|
|
469
|
+
const products = sqliteTable('products', {
|
|
470
|
+
id: integer('id').primaryKey(),
|
|
471
|
+
name: text('name').notNull(),
|
|
472
|
+
description: text('description'),
|
|
473
|
+
price: real('price').notNull(),
|
|
474
|
+
stock: integer('stock').default(0)
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
const app = server({
|
|
478
|
+
database: {
|
|
479
|
+
connection: './store.db',
|
|
480
|
+
schema: { products }
|
|
481
|
+
},
|
|
482
|
+
routes : [
|
|
483
|
+
{
|
|
484
|
+
method : 'GET',
|
|
485
|
+
path : '/products',
|
|
486
|
+
handler : async (c) => {
|
|
487
|
+
// Query with filters
|
|
488
|
+
const allProducts = await c.db
|
|
489
|
+
.select()
|
|
490
|
+
.from(products)
|
|
491
|
+
.where(and(
|
|
492
|
+
eq(products.stock, 0),
|
|
493
|
+
like(products.name, '%laptop%')
|
|
494
|
+
));
|
|
495
|
+
|
|
496
|
+
return c.json(allProducts);
|
|
497
|
+
}
|
|
498
|
+
},
|
|
499
|
+
{
|
|
500
|
+
method : 'POST',
|
|
501
|
+
path : '/products',
|
|
502
|
+
handler : async (c) => {
|
|
503
|
+
// Insert new product
|
|
504
|
+
const newProduct = await c.db
|
|
505
|
+
.insert(products)
|
|
506
|
+
.values(c.body)
|
|
507
|
+
.returning();
|
|
508
|
+
|
|
509
|
+
return c.json(newProduct[0]);
|
|
510
|
+
}
|
|
511
|
+
},
|
|
512
|
+
{
|
|
513
|
+
method : 'PUT',
|
|
514
|
+
path : '/products/:id',
|
|
515
|
+
handler : async (c) => {
|
|
516
|
+
// Update product
|
|
517
|
+
const updated = await c.db
|
|
518
|
+
.update(products)
|
|
519
|
+
.set(c.body)
|
|
520
|
+
.where(eq(products.id, parseInt(c.params.id)))
|
|
521
|
+
.returning();
|
|
522
|
+
|
|
523
|
+
return c.json(updated[0]);
|
|
524
|
+
}
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
method : 'DELETE',
|
|
528
|
+
path : '/products/:id',
|
|
529
|
+
handler : async (c) => {
|
|
530
|
+
// Delete product
|
|
531
|
+
await c.db
|
|
532
|
+
.delete(products)
|
|
533
|
+
.where(eq(products.id, parseInt(c.params.id)));
|
|
534
|
+
|
|
535
|
+
return c.json({ deleted: true });
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
]
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
await app.start();
|
|
542
|
+
// All data saved to ./store.db and persists across restarts!
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
<div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
|
|
546
|
+
|
|
547
|
+
- ## Advanced Features
|
|
548
|
+
|
|
549
|
+
- ### Logging
|
|
550
|
+
|
|
551
|
+
```typescript
|
|
552
|
+
import { server, Logger } from '@je-es/server';
|
|
553
|
+
|
|
554
|
+
// Enable logging
|
|
555
|
+
const app = server({
|
|
556
|
+
logging: {
|
|
557
|
+
level: 'debug', // 'debug' | 'info' | 'warn' | 'error' | 'fatal'
|
|
558
|
+
pretty: true // Pretty print for development
|
|
559
|
+
}
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
// Use logger in routes
|
|
563
|
+
const route = {
|
|
564
|
+
method : 'GET',
|
|
565
|
+
path : '/test',
|
|
566
|
+
handler : (c) => {
|
|
567
|
+
c.logger?.info({ userId: 123 }, 'User accessed endpoint');
|
|
568
|
+
c.logger?.warn({ attempt: 3 }, 'Suspicious activity');
|
|
569
|
+
c.logger?.error({ error: 'DB connection failed' }, 'Database error');
|
|
570
|
+
return c.json({ ok: true });
|
|
571
|
+
}
|
|
572
|
+
};
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
- ### Cookie Management
|
|
576
|
+
|
|
577
|
+
```typescript
|
|
578
|
+
const app = server({
|
|
579
|
+
routes : [
|
|
580
|
+
{
|
|
581
|
+
method : 'POST',
|
|
582
|
+
path : '/login',
|
|
583
|
+
handler : (c) => {
|
|
584
|
+
// Set cookie with options
|
|
585
|
+
c.setCookie('session', 'user-token-123', {
|
|
586
|
+
maxAge: 3600, // 1 hour
|
|
587
|
+
expires: new Date('2025-12-31'),
|
|
588
|
+
path : '/',
|
|
589
|
+
domain: 'example.com',
|
|
590
|
+
secure: true, // HTTPS only
|
|
591
|
+
httpOnly: true, // No JavaScript access
|
|
592
|
+
sameSite: 'Strict' // CSRF protection
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
return c.json({ loggedIn: true });
|
|
596
|
+
}
|
|
597
|
+
},
|
|
598
|
+
{
|
|
599
|
+
method : 'GET',
|
|
600
|
+
path : '/profile',
|
|
601
|
+
handler : (c) => {
|
|
602
|
+
const session = c.getCookie('session');
|
|
603
|
+
if (!session) {
|
|
604
|
+
return c.status(401).json({ error: 'Unauthorized' });
|
|
605
|
+
}
|
|
606
|
+
return c.json({ session });
|
|
607
|
+
}
|
|
608
|
+
},
|
|
609
|
+
{
|
|
610
|
+
method : 'POST',
|
|
611
|
+
path : '/logout',
|
|
612
|
+
handler : (c) => {
|
|
613
|
+
c.deleteCookie('session');
|
|
614
|
+
return c.json({ loggedOut: true });
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
]
|
|
618
|
+
});
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
- ### Dynamic Routing
|
|
622
|
+
|
|
623
|
+
```typescript
|
|
624
|
+
const app = server({
|
|
625
|
+
routes : [
|
|
626
|
+
// Simple parameter
|
|
627
|
+
{
|
|
628
|
+
method : 'GET',
|
|
629
|
+
path : '/users/:id',
|
|
630
|
+
handler : (c) => c.json({ userId: c.params.id })
|
|
631
|
+
},
|
|
632
|
+
|
|
633
|
+
// Multiple parameters
|
|
634
|
+
{
|
|
635
|
+
method : 'GET',
|
|
636
|
+
path : '/posts/:postId/comments/:commentId',
|
|
637
|
+
handler : (c) => c.json({
|
|
638
|
+
postId: c.params.postId,
|
|
639
|
+
commentId: c.params.commentId
|
|
640
|
+
})
|
|
641
|
+
},
|
|
642
|
+
|
|
643
|
+
// Complex patterns
|
|
644
|
+
{
|
|
645
|
+
method : 'GET',
|
|
646
|
+
path : '/api/:version/:resource',
|
|
647
|
+
handler : (c) => c.json({
|
|
648
|
+
version: c.params.version,
|
|
649
|
+
resource: c.params.resource
|
|
650
|
+
})
|
|
651
|
+
}
|
|
652
|
+
]
|
|
653
|
+
});
|
|
654
|
+
```
|
|
655
|
+
|
|
656
|
+
- ### Health Checks
|
|
657
|
+
|
|
658
|
+
```typescript
|
|
659
|
+
// Built-in health endpoints are automatically available
|
|
660
|
+
|
|
661
|
+
// GET /health
|
|
662
|
+
// Response:
|
|
663
|
+
{
|
|
664
|
+
status: 'healthy',
|
|
665
|
+
timestamp: '2025-11-28T10:00:00.000Z',
|
|
666
|
+
uptime: 3600,
|
|
667
|
+
activeRequests: 5
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// GET /readiness
|
|
671
|
+
// Response:
|
|
672
|
+
{
|
|
673
|
+
ready: true,
|
|
674
|
+
checks: {
|
|
675
|
+
database: 'connected', // or 'not configured'
|
|
676
|
+
activeRequests: 5
|
|
677
|
+
},
|
|
678
|
+
timestamp: '2025-11-28T10:00:00.000Z'
|
|
679
|
+
}
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
- ### Graceful Shutdown
|
|
683
|
+
|
|
684
|
+
```typescript
|
|
685
|
+
const app = server({
|
|
686
|
+
gracefulShutdownTimeout: 10000, // 10 seconds
|
|
687
|
+
onShutdown: async () => {
|
|
688
|
+
console.log('Cleaning up resources...');
|
|
689
|
+
// Close external connections, flush logs, etc.
|
|
690
|
+
}
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
await app.start();
|
|
694
|
+
|
|
695
|
+
// Handle signals
|
|
696
|
+
process.on('SIGTERM', async () => {
|
|
697
|
+
await app.stop();
|
|
698
|
+
process.exit(0);
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
process.on('SIGINT', async () => {
|
|
702
|
+
await app.stop();
|
|
703
|
+
process.exit(0);
|
|
704
|
+
});
|
|
705
|
+
```
|
|
706
|
+
|
|
707
|
+
- ### Dynamic Routes
|
|
708
|
+
|
|
709
|
+
```typescript
|
|
710
|
+
const app = server({
|
|
711
|
+
routes : [
|
|
712
|
+
{
|
|
713
|
+
method : 'GET',
|
|
714
|
+
path : '/initial',
|
|
715
|
+
handler : (c) => c.json({ route: 'initial' })
|
|
716
|
+
}
|
|
717
|
+
]
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
await app.start();
|
|
721
|
+
|
|
722
|
+
// Add routes after server starts
|
|
723
|
+
app.addRoute({
|
|
724
|
+
method : 'POST',
|
|
725
|
+
path : '/dynamic',
|
|
726
|
+
handler : (c) => c.json({ route: 'dynamic', body: c.body })
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
// Get all registered routes
|
|
730
|
+
const routes = app.getRoutes();
|
|
731
|
+
console.log(routes);
|
|
732
|
+
```
|
|
733
|
+
|
|
734
|
+
- ### Request Timeout
|
|
735
|
+
|
|
736
|
+
```typescript
|
|
737
|
+
const app = server({
|
|
738
|
+
requestTimeout : 5000, // 5 seconds
|
|
739
|
+
routes : [
|
|
740
|
+
{
|
|
741
|
+
method : 'GET',
|
|
742
|
+
path : '/slow',
|
|
743
|
+
handler : async (c) => {
|
|
744
|
+
// If this takes more than 5 seconds, request will timeout
|
|
745
|
+
await someSlowOperation();
|
|
746
|
+
return c.json({ done: true });
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
]
|
|
750
|
+
});
|
|
751
|
+
```
|
|
752
|
+
|
|
753
|
+
- ### Custom Error Handling
|
|
754
|
+
|
|
755
|
+
```typescript
|
|
756
|
+
import { AppError, ValidationError } from '@je-es/server';
|
|
757
|
+
|
|
758
|
+
const app = server({
|
|
759
|
+
routes : [
|
|
760
|
+
{
|
|
761
|
+
method : 'POST',
|
|
762
|
+
path : '/validate',
|
|
763
|
+
handler : (c) => {
|
|
764
|
+
if (!c.body?.email) {
|
|
765
|
+
throw new ValidationError('Email is required');
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
if (!c.body.email.includes('@')) {
|
|
769
|
+
throw new AppError('Invalid email format', 400, 'INVALID_EMAIL');
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
return c.json({ valid: true });
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
]
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
// Error responses are automatically formatted:
|
|
779
|
+
{
|
|
780
|
+
error : 'Email is required',
|
|
781
|
+
code : 'VALIDATION_ERROR',
|
|
782
|
+
requestId : 'unique-request-id'
|
|
783
|
+
}
|
|
784
|
+
```
|
|
785
|
+
|
|
786
|
+
<!-- ╚═════════════════════════════════════════════════════════════════╝ -->
|
|
787
|
+
|
|
788
|
+
|
|
789
|
+
|
|
790
|
+
<!-- ╔══════════════════════════════ END ══════════════════════════════╗ -->
|
|
791
|
+
|
|
792
|
+
<br>
|
|
793
|
+
<div align="center">
|
|
794
|
+
<img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/>
|
|
795
|
+
<br>
|
|
796
|
+
</div>
|
|
797
|
+
<br>
|
|
798
|
+
<div align="center">
|
|
799
|
+
<a href="https://github.com/solution-lib/space"><img src="https://img.shields.io/badge/by-Space-black"/></a>
|
|
800
|
+
</div>
|
|
801
|
+
|
|
802
|
+
<!-- ╚═════════════════════════════════════════════════════════════════╝ -->
|