@je-es/server 0.1.2 → 0.1.3
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 +332 -837
- package/dist/main.cjs +2 -2
- package/dist/main.cjs.map +1 -1
- package/dist/main.d.cts +200 -266
- package/dist/main.d.ts +200 -266
- package/dist/main.js +2 -2
- package/dist/main.js.map +1 -1
- package/package.json +7 -4
package/README.md
CHANGED
|
@@ -8,10 +8,13 @@
|
|
|
8
8
|
</div>
|
|
9
9
|
|
|
10
10
|
<div align="center">
|
|
11
|
-
<img src="https://img.shields.io/badge/v-0.1.
|
|
12
|
-
<
|
|
13
|
-
</
|
|
14
|
-
<
|
|
11
|
+
<img src="https://img.shields.io/badge/v-0.1.3-black"/>
|
|
12
|
+
<img src="https://img.shields.io/badge/🔥-@je--es-black"/>
|
|
13
|
+
<div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
|
|
14
|
+
<img src="https://github.com/je-es/server/actions/workflows/ci.yml/badge.svg" alt="CI" />
|
|
15
|
+
<img src="https://img.shields.io/badge/coverage-100%25-brightgreen" alt="Test Coverage" />
|
|
16
|
+
<img src="https://img.shields.io/github/issues/je-es/server?style=flat" alt="Github Repo Issues" />
|
|
17
|
+
<img src="https://img.shields.io/github/stars/je-es/server?style=social" alt="GitHub Repo stars" />
|
|
15
18
|
</div>
|
|
16
19
|
<br>
|
|
17
20
|
|
|
@@ -24,181 +27,175 @@
|
|
|
24
27
|
|
|
25
28
|
- ## Quick Start 🔥
|
|
26
29
|
|
|
27
|
-
> _**The simplest, fastest, most organized
|
|
30
|
+
> _**The simplest, fastest, and most organized way to build production-ready servers with Bun.**_
|
|
28
31
|
|
|
29
32
|
> _We prefer to use [`space`](https://github.com//solution-lib/space) with [`@solution-dist/server`](https://github.com/solution-dist/server) for a better experience._
|
|
30
33
|
|
|
31
|
-
-
|
|
34
|
+
- ### Setup
|
|
32
35
|
|
|
33
36
|
> install [`space`](https://github.com/solution-lib/space) first.
|
|
34
37
|
|
|
35
|
-
-
|
|
38
|
+
- #### Create
|
|
36
39
|
|
|
37
40
|
```bash
|
|
38
|
-
space init <name> -t server # This will clone a ready-to-use repo and make some changes to suit your server.
|
|
39
|
-
cd <name> # Go to the project directory
|
|
40
|
-
space install # Install the dependencies
|
|
41
|
+
> space init <name> -t server # This will clone a ready-to-use repo and make some changes to suit your server.
|
|
42
|
+
> cd <name> # Go to the project directory
|
|
43
|
+
> space install # Install the dependencies
|
|
41
44
|
```
|
|
42
45
|
|
|
43
|
-
-
|
|
46
|
+
- #### Manage
|
|
44
47
|
|
|
45
48
|
```bash
|
|
46
|
-
space
|
|
47
|
-
space
|
|
48
|
-
space
|
|
49
|
+
> space lint
|
|
50
|
+
> space build
|
|
51
|
+
> space test
|
|
52
|
+
> space start
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
- #### Usage
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
import { server } from '@je-es/server';
|
|
59
|
+
|
|
60
|
+
const app = server({
|
|
61
|
+
port : 3000,
|
|
62
|
+
routes : [
|
|
63
|
+
{
|
|
64
|
+
method : 'GET',
|
|
65
|
+
path : '/',
|
|
66
|
+
handler : (c) => c.json({ message: 'Hello World!' })
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
await app.start();
|
|
49
72
|
```
|
|
50
73
|
|
|
51
74
|
```bash
|
|
52
|
-
# example
|
|
53
75
|
> space start
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
16:16:31 ✓ Server started at http://localhost:3000
|
|
57
|
-
16:17:25 GET / 200 4ms
|
|
76
|
+
16:16:31 Server started at http://localhost:3000
|
|
77
|
+
16:17:25 GET / 200 1ms
|
|
58
78
|
...
|
|
59
79
|
```
|
|
60
80
|
|
|
61
|
-
<br>
|
|
62
|
-
|
|
81
|
+
<div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
|
|
63
82
|
|
|
64
83
|
- ## Examples
|
|
65
84
|
|
|
66
85
|
- ### Basic Server
|
|
67
86
|
|
|
68
87
|
```typescript
|
|
69
|
-
// import
|
|
70
88
|
import { server } from '@je-es/server';
|
|
71
|
-
```
|
|
72
89
|
|
|
73
|
-
```typescript
|
|
74
|
-
// create
|
|
75
90
|
const app = server({
|
|
76
91
|
port : 3000,
|
|
92
|
+
logging : { level: 'info', pretty: true },
|
|
77
93
|
routes : [
|
|
78
|
-
{
|
|
79
|
-
method : 'GET',
|
|
80
|
-
path : '/',
|
|
81
|
-
handler : (c) => c.json({ message: 'Hello World!' })
|
|
82
|
-
},
|
|
83
94
|
{
|
|
84
95
|
method : 'GET',
|
|
85
96
|
path : '/users/:id',
|
|
86
|
-
handler : (c) => {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
97
|
+
handler : (c) => c.json({
|
|
98
|
+
userId : c.params.id,
|
|
99
|
+
ip : c.ip,
|
|
100
|
+
requestId : c.requestId
|
|
101
|
+
})
|
|
90
102
|
},
|
|
91
103
|
{
|
|
92
|
-
method
|
|
93
|
-
path
|
|
94
|
-
handler
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
104
|
+
method : 'POST',
|
|
105
|
+
path : '/users',
|
|
106
|
+
handler : (c) => c.status(201).json({
|
|
107
|
+
created : true,
|
|
108
|
+
data : c.body
|
|
109
|
+
})
|
|
98
110
|
}
|
|
99
111
|
]
|
|
100
112
|
});
|
|
101
|
-
```
|
|
102
113
|
|
|
103
|
-
```typescript
|
|
104
|
-
// start
|
|
105
114
|
await app.start();
|
|
106
115
|
```
|
|
107
116
|
|
|
108
|
-
>
|
|
109
|
-
|
|
110
|
-
```typescript
|
|
111
|
-
> space start
|
|
112
|
-
|
|
113
|
-
→ URL: http://localhost:3000
|
|
114
|
-
→ Environment: test
|
|
115
|
-
→ Routes: N
|
|
116
|
-
...
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
<div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
|
|
117
|
+
<div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
|
|
120
118
|
|
|
121
119
|
- ### With Database
|
|
122
120
|
|
|
123
121
|
```typescript
|
|
124
122
|
import { server, table, integer, text, primaryKey, notNull } from '@je-es/server';
|
|
125
123
|
|
|
126
|
-
// Define your schema using the built-in schema builder
|
|
127
124
|
const users = table('users', [
|
|
128
|
-
primaryKey(integer('id'), true),
|
|
125
|
+
primaryKey(integer('id'), true),
|
|
129
126
|
notNull(text('name')),
|
|
130
127
|
notNull(text('email'))
|
|
131
128
|
]);
|
|
132
129
|
|
|
133
130
|
const app = server({
|
|
134
|
-
port
|
|
135
|
-
database: {
|
|
136
|
-
connection : './
|
|
131
|
+
port : 3000,
|
|
132
|
+
database : {
|
|
133
|
+
connection : './app.db',
|
|
137
134
|
schema : { users }
|
|
138
135
|
},
|
|
139
|
-
routes
|
|
136
|
+
routes: [
|
|
140
137
|
{
|
|
141
138
|
method : 'GET',
|
|
142
139
|
path : '/users',
|
|
143
|
-
handler : (c) =>
|
|
144
|
-
const allUsers = c.db.all('users');
|
|
145
|
-
return c.json(allUsers);
|
|
146
|
-
}
|
|
140
|
+
handler : (c) => c.json(c.db!.all('users'))
|
|
147
141
|
},
|
|
148
142
|
{
|
|
149
143
|
method : 'POST',
|
|
150
144
|
path : '/users',
|
|
145
|
+
handler : (c) => c.json(c.db!.insert('users', c.body))
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
method : 'GET',
|
|
149
|
+
path : '/users/:id',
|
|
151
150
|
handler : (c) => {
|
|
152
|
-
const
|
|
153
|
-
return
|
|
151
|
+
const user = c.db!.findById('users', parseInt(c.params.id));
|
|
152
|
+
return user
|
|
153
|
+
? c.json(user)
|
|
154
|
+
: c.status(404).json({ error: 'Not found' });
|
|
154
155
|
}
|
|
155
156
|
}
|
|
156
157
|
]
|
|
157
158
|
});
|
|
158
159
|
|
|
159
160
|
await app.start();
|
|
160
|
-
// Data persists in ./my_app.db file!
|
|
161
161
|
```
|
|
162
162
|
|
|
163
|
-
|
|
163
|
+
<div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
|
|
164
164
|
|
|
165
165
|
- ### With Security
|
|
166
166
|
|
|
167
167
|
```typescript
|
|
168
|
-
import { server } from '@je-es/server';
|
|
169
|
-
|
|
170
168
|
const app = server({
|
|
171
|
-
port
|
|
172
|
-
security: {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
message : 'Too many requests, please try again later'
|
|
178
|
-
},
|
|
179
|
-
// CORS configuration
|
|
180
|
-
cors: {
|
|
181
|
-
origin : ['http://localhost:3000', 'https://example.com'],
|
|
182
|
-
credentials : true,
|
|
183
|
-
methods : ['GET', 'POST', 'PUT', 'DELETE'],
|
|
184
|
-
allowedHeaders : ['Content-Type', 'Authorization']
|
|
169
|
+
port : 3000,
|
|
170
|
+
security : {
|
|
171
|
+
rateLimit : { max: 100, windowMs: 60000 },
|
|
172
|
+
cors : {
|
|
173
|
+
origin : ['http://localhost:3000'],
|
|
174
|
+
credentials : true
|
|
185
175
|
}
|
|
186
176
|
},
|
|
187
|
-
routes
|
|
188
|
-
{
|
|
189
|
-
method : 'GET',
|
|
190
|
-
path : '/protected',
|
|
191
|
-
handler : (c) => c.json({ message: 'This route is protected!' })
|
|
192
|
-
}
|
|
193
|
-
]
|
|
177
|
+
routes: [/* your routes */]
|
|
194
178
|
});
|
|
179
|
+
```
|
|
195
180
|
|
|
196
|
-
|
|
181
|
+
<div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
|
|
182
|
+
|
|
183
|
+
- ### Static Files
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
const app = server({
|
|
187
|
+
port : 3000,
|
|
188
|
+
static : {
|
|
189
|
+
path : '/public',
|
|
190
|
+
directory : './public',
|
|
191
|
+
maxAge : 3600
|
|
192
|
+
}
|
|
193
|
+
});
|
|
197
194
|
```
|
|
198
195
|
|
|
199
|
-
<br>
|
|
196
|
+
<br>
|
|
200
197
|
|
|
201
|
-
- ## API
|
|
198
|
+
- ## API
|
|
202
199
|
|
|
203
200
|
- ### Server Configuration
|
|
204
201
|
|
|
@@ -206,866 +203,364 @@
|
|
|
206
203
|
import { server, type ServerConfig } from '@je-es/server';
|
|
207
204
|
|
|
208
205
|
const config: ServerConfig = {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
hostname : 'localhost',
|
|
206
|
+
port : 3000,
|
|
207
|
+
hostname : 'localhost',
|
|
212
208
|
|
|
213
|
-
//
|
|
214
|
-
requestTimeout
|
|
215
|
-
maxRequestSize
|
|
216
|
-
gracefulShutdownTimeout
|
|
209
|
+
// Timeouts & Limits
|
|
210
|
+
requestTimeout : 30000,
|
|
211
|
+
maxRequestSize : 10485760,
|
|
212
|
+
gracefulShutdownTimeout : 10000,
|
|
217
213
|
|
|
218
|
-
// Logging
|
|
214
|
+
// Logging (via @je-es/slog)
|
|
219
215
|
logging: {
|
|
220
|
-
level
|
|
221
|
-
pretty
|
|
216
|
+
level : 'info', // 'debug' | 'info' | 'warn' | 'error'
|
|
217
|
+
pretty : false
|
|
222
218
|
},
|
|
223
219
|
|
|
224
|
-
// Database
|
|
220
|
+
// Database (via @je-es/sdb)
|
|
225
221
|
database: {
|
|
226
|
-
connection
|
|
227
|
-
schema
|
|
222
|
+
connection : './app.db', // or ':memory:'
|
|
223
|
+
schema : {}
|
|
228
224
|
},
|
|
229
225
|
|
|
226
|
+
// Multiple databases
|
|
227
|
+
database: [
|
|
228
|
+
{ name: 'default', connection: './main.db' },
|
|
229
|
+
{ name: 'cache', connection: ':memory:' }
|
|
230
|
+
],
|
|
231
|
+
|
|
230
232
|
// Security
|
|
231
233
|
security: {
|
|
232
|
-
rateLimit
|
|
233
|
-
cors
|
|
234
|
-
csrf : true
|
|
234
|
+
rateLimit : { max: 100, windowMs: 60000 },
|
|
235
|
+
cors : { origin: '*', credentials: true }
|
|
235
236
|
},
|
|
236
237
|
|
|
237
|
-
//
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
}
|
|
244
|
-
};
|
|
245
|
-
|
|
246
|
-
const app = server(config);
|
|
247
|
-
await app.start();
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
<div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
|
|
251
|
-
|
|
252
|
-
- ### Route Definition
|
|
253
|
-
|
|
254
|
-
```typescript
|
|
255
|
-
import { type RouteDefinition, type AppContext } from '@je-es/server';
|
|
256
|
-
|
|
257
|
-
// Single HTTP method
|
|
258
|
-
const route: RouteDefinition = {
|
|
259
|
-
method : 'GET',
|
|
260
|
-
path : '/users/:id',
|
|
261
|
-
handler : (c: AppContext) => {
|
|
262
|
-
return c.json({ id: c.params.id });
|
|
263
|
-
}
|
|
264
|
-
};
|
|
265
|
-
|
|
266
|
-
// Multiple HTTP methods
|
|
267
|
-
const multiMethodRoute: RouteDefinition = {
|
|
268
|
-
method : ['GET', 'POST'],
|
|
269
|
-
path : '/api/resource',
|
|
270
|
-
handler : (c: AppContext) => {
|
|
271
|
-
if (c.request.method === 'GET') {
|
|
272
|
-
return c.json({ method : 'GET' });
|
|
273
|
-
}
|
|
274
|
-
return c.json({ method : 'POST', body: c.body });
|
|
275
|
-
}
|
|
276
|
-
};
|
|
238
|
+
// Static files
|
|
239
|
+
static: {
|
|
240
|
+
path : '/public',
|
|
241
|
+
directory : './public',
|
|
242
|
+
maxAge : 3600
|
|
243
|
+
},
|
|
277
244
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
method : 'GET',
|
|
281
|
-
path : '/posts/:postId/comments/:commentId',
|
|
282
|
-
handler : (c: AppContext) => {
|
|
283
|
-
return c.json({
|
|
284
|
-
postId: c.params.postId,
|
|
285
|
-
commentId: c.params.commentId
|
|
286
|
-
});
|
|
287
|
-
}
|
|
245
|
+
// Lifecycle
|
|
246
|
+
onShutdown : async () => { console.log('Shutting down...'); }
|
|
288
247
|
};
|
|
289
248
|
```
|
|
290
249
|
|
|
291
|
-
|
|
250
|
+
<div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
|
|
292
251
|
|
|
293
252
|
- ### Context API
|
|
294
253
|
|
|
295
254
|
```typescript
|
|
296
|
-
import { type AppContext } from '@je-es/server';
|
|
297
|
-
|
|
298
|
-
// Response methods
|
|
299
|
-
handler : (c: AppContext) => {
|
|
300
|
-
// JSON response
|
|
301
|
-
c.json({ data: 'value' }, 200);
|
|
302
|
-
|
|
303
|
-
// Text response
|
|
304
|
-
c.text('Hello World', 200);
|
|
305
|
-
|
|
306
|
-
// HTML response
|
|
307
|
-
c.html('<h1>Hello</h1>', 200);
|
|
308
|
-
|
|
309
|
-
// Redirect
|
|
310
|
-
c.redirect('/new-location', 302);
|
|
311
|
-
|
|
312
|
-
// File response
|
|
313
|
-
c.file('./path/to/file.pdf', 'application/pdf');
|
|
314
|
-
|
|
315
|
-
// Chain status
|
|
316
|
-
c.status(201).json({ created: true });
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
// Request data
|
|
320
|
-
handler : (c: AppContext) => {
|
|
321
|
-
const params = c.params; // URL parameters
|
|
322
|
-
const query = c.query; // Query string
|
|
323
|
-
const body = c.body; // Request body
|
|
324
|
-
const headers = c.headers; // Request headers
|
|
325
|
-
const db = c.db; // Database instance
|
|
326
|
-
const logger = c.logger; // Logger instance
|
|
327
|
-
const requestId = c.requestId; // Unique request ID
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// Headers
|
|
331
255
|
handler : (c: AppContext) => {
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
c.
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
c.
|
|
256
|
+
// Request
|
|
257
|
+
c.params // URL parameters
|
|
258
|
+
c.query // Query string
|
|
259
|
+
c.body // Parsed body (JSON/form/multipart)
|
|
260
|
+
c.headers // Request headers
|
|
261
|
+
c.ip // Client IP
|
|
262
|
+
c.requestId // Unique request ID
|
|
263
|
+
|
|
264
|
+
// Resources
|
|
265
|
+
c.db // Database instance
|
|
266
|
+
c.logger // Logger instance
|
|
267
|
+
|
|
268
|
+
// Response
|
|
269
|
+
c.json({ data })
|
|
270
|
+
c.text('text')
|
|
271
|
+
c.html('HTML')
|
|
272
|
+
c.redirect('/path')
|
|
273
|
+
c.file('./file.pdf', 'application/pdf')
|
|
274
|
+
c.status(201).json({ created: true })
|
|
275
|
+
|
|
276
|
+
// Headers
|
|
277
|
+
c.setHeader('X-Custom', 'value')
|
|
278
|
+
c.getHeader('Authorization')
|
|
279
|
+
|
|
280
|
+
// Cookies
|
|
281
|
+
c.setCookie('session', 'token', { httpOnly: true })
|
|
282
|
+
c.getCookie('session')
|
|
283
|
+
c.deleteCookie('session')
|
|
347
284
|
}
|
|
348
285
|
```
|
|
349
286
|
|
|
350
|
-
<br>
|
|
287
|
+
<div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
|
|
351
288
|
|
|
352
|
-
-
|
|
353
|
-
|
|
354
|
-
- ### Rate Limiting
|
|
289
|
+
- ### Routes
|
|
355
290
|
|
|
356
291
|
```typescript
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
rateLimit: {
|
|
360
|
-
max: 100, // Max requests per window
|
|
361
|
-
windowMs: 60000, // Time window in milliseconds
|
|
362
|
-
keyGenerator: (c) => {
|
|
363
|
-
// Custom key generation (default: IP address)
|
|
364
|
-
return c.headers.get('x-api-key') || c.request.ip;
|
|
365
|
-
},
|
|
366
|
-
message: 'Rate limit exceeded'
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
});
|
|
370
|
-
```
|
|
292
|
+
// Single method
|
|
293
|
+
{ method : 'GET', path : '/users', handler }
|
|
371
294
|
|
|
372
|
-
|
|
295
|
+
// Multiple methods
|
|
296
|
+
{ method : ['GET', 'POST'], path : '/api', handler }
|
|
373
297
|
|
|
374
|
-
|
|
298
|
+
// Dynamic parameters
|
|
299
|
+
{ method : 'GET', path : '/users/:id', handler }
|
|
300
|
+
{ method : 'GET', path : '/posts/:postId/comments/:commentId', handler }
|
|
375
301
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
security: {
|
|
379
|
-
cors: {
|
|
380
|
-
// Allow specific origins
|
|
381
|
-
origin: ['http://localhost:3000', 'https://example.com'],
|
|
382
|
-
|
|
383
|
-
// Or use a function
|
|
384
|
-
origin: (origin) => {
|
|
385
|
-
return origin.endsWith('.example.com');
|
|
386
|
-
},
|
|
387
|
-
|
|
388
|
-
// Or allow all
|
|
389
|
-
origin: '*',
|
|
390
|
-
|
|
391
|
-
methods: ['GET', 'POST', 'PUT', 'DELETE'],
|
|
392
|
-
allowedHeaders: ['Content-Type', 'Authorization'],
|
|
393
|
-
credentials: true,
|
|
394
|
-
maxAge: 86400 // 24 hours
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
});
|
|
302
|
+
// Wildcards
|
|
303
|
+
{ method : 'GET', path : '/files/*', handler }
|
|
398
304
|
```
|
|
399
305
|
|
|
400
|
-
|
|
306
|
+
<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
307
|
|
|
402
|
-
- ###
|
|
308
|
+
- ### Database Operations
|
|
403
309
|
|
|
404
310
|
```typescript
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
311
|
+
// CRUD
|
|
312
|
+
c.db!.all ('users')
|
|
313
|
+
c.db!.findById ('users', 1)
|
|
314
|
+
c.db!.find ('users', { role: 'admin' })
|
|
315
|
+
c.db!.insert ('users', { name: 'John' })
|
|
316
|
+
c.db!.update ('users', 1, { name: 'Jane' })
|
|
317
|
+
c.db!.delete ('users', 1)
|
|
318
|
+
|
|
319
|
+
// Multiple databases
|
|
320
|
+
const mainDb = app.db.get('default');
|
|
321
|
+
const cacheDb = app.db.get('cache');
|
|
414
322
|
```
|
|
415
323
|
|
|
416
|
-
|
|
324
|
+
<div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
|
|
417
325
|
|
|
418
|
-
- ###
|
|
326
|
+
- ### Dynamic Routes
|
|
419
327
|
|
|
420
328
|
```typescript
|
|
421
|
-
|
|
329
|
+
await app.start();
|
|
422
330
|
|
|
423
|
-
|
|
331
|
+
// Add single route
|
|
332
|
+
app.addRoute({
|
|
333
|
+
method : 'POST',
|
|
334
|
+
path : '/dynamic',
|
|
335
|
+
handler : (c) => c.json({ dynamic: true })
|
|
336
|
+
});
|
|
424
337
|
|
|
425
|
-
//
|
|
426
|
-
|
|
427
|
-
|
|
338
|
+
// Add multiple routes
|
|
339
|
+
app.addRoutes([
|
|
340
|
+
{ method : 'GET', path : '/route1', handler },
|
|
341
|
+
{ method : 'GET', path : '/route2', handler }
|
|
342
|
+
]);
|
|
428
343
|
|
|
429
|
-
//
|
|
430
|
-
const
|
|
431
|
-
// Output: ''; DROP TABLE users--
|
|
344
|
+
// Get all routes
|
|
345
|
+
const routes = app.getRoutes();
|
|
432
346
|
```
|
|
433
347
|
|
|
434
|
-
<br>
|
|
348
|
+
<br>
|
|
435
349
|
|
|
436
|
-
- ##
|
|
350
|
+
- ## Security
|
|
437
351
|
|
|
438
|
-
- ###
|
|
352
|
+
- ### Rate Limiting
|
|
439
353
|
|
|
440
354
|
```typescript
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
355
|
+
security: {
|
|
356
|
+
rateLimit: {
|
|
357
|
+
max : 100,
|
|
358
|
+
windowMs : 60000,
|
|
359
|
+
keyGenerator : (c) => c.ip,
|
|
360
|
+
message : 'Too many requests'
|
|
447
361
|
}
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
// Access in routes via c.db
|
|
451
|
-
```
|
|
452
|
-
|
|
453
|
-
<div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
|
|
454
|
-
|
|
455
|
-
- ### Multiple Databases
|
|
456
|
-
|
|
457
|
-
```typescript
|
|
458
|
-
import { server } from '@je-es/server';
|
|
459
|
-
|
|
460
|
-
const app = server({
|
|
461
|
-
database: [
|
|
462
|
-
{
|
|
463
|
-
name: 'default',
|
|
464
|
-
connection: './main.db' // Main database file
|
|
465
|
-
},
|
|
466
|
-
{
|
|
467
|
-
name: 'analytics',
|
|
468
|
-
connection: './analytics.db' // Analytics database file
|
|
469
|
-
}
|
|
470
|
-
],
|
|
471
|
-
routes : [
|
|
472
|
-
{
|
|
473
|
-
method : 'GET',
|
|
474
|
-
path : '/data',
|
|
475
|
-
handler : (c) => {
|
|
476
|
-
// Access default database
|
|
477
|
-
const users = c.db.all('users');
|
|
478
|
-
|
|
479
|
-
// Access named databases
|
|
480
|
-
const mainDb = app.db.get('default');
|
|
481
|
-
const analyticsDb = app.db.get('analytics');
|
|
482
|
-
|
|
483
|
-
const mainData = mainDb.all('some_table');
|
|
484
|
-
const analyticsData = analyticsDb.all('analytics_table');
|
|
485
|
-
|
|
486
|
-
return c.json({ users, mainData, analyticsData });
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
]
|
|
490
|
-
});
|
|
362
|
+
}
|
|
491
363
|
```
|
|
492
364
|
|
|
493
|
-
|
|
365
|
+
<div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
|
|
494
366
|
|
|
495
|
-
- ###
|
|
367
|
+
- ### CORS
|
|
496
368
|
|
|
497
369
|
```typescript
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
numeric,
|
|
506
|
-
primaryKey,
|
|
507
|
-
notNull,
|
|
508
|
-
unique,
|
|
509
|
-
defaultValue,
|
|
510
|
-
references
|
|
511
|
-
} from '@je-es/server';
|
|
512
|
-
|
|
513
|
-
// Define products table
|
|
514
|
-
const products = table('products', [
|
|
515
|
-
primaryKey(integer('id'), true), // Auto-increment primary key
|
|
516
|
-
notNull(text('name')),
|
|
517
|
-
text('description'),
|
|
518
|
-
notNull(real('price')),
|
|
519
|
-
defaultValue(integer('stock'), 0)
|
|
520
|
-
]);
|
|
521
|
-
|
|
522
|
-
// Define orders table with foreign key
|
|
523
|
-
const orders = table('orders', [
|
|
524
|
-
primaryKey(integer('id'), true),
|
|
525
|
-
notNull(integer('product_id')),
|
|
526
|
-
references(integer('product_id'), 'products', 'id'),
|
|
527
|
-
notNull(integer('quantity')),
|
|
528
|
-
defaultValue(text('status'), 'pending')
|
|
529
|
-
]);
|
|
530
|
-
|
|
531
|
-
const app = server({
|
|
532
|
-
database: {
|
|
533
|
-
connection: './store.db',
|
|
534
|
-
schema: { products, orders }
|
|
370
|
+
security: {
|
|
371
|
+
cors: {
|
|
372
|
+
origin : ['http://localhost:3000'],
|
|
373
|
+
// or: origin : '*',
|
|
374
|
+
// or: origin : (origin) => origin.endsWith('.example.com'),
|
|
375
|
+
methods : ['GET', 'POST', 'PUT', 'DELETE'],
|
|
376
|
+
credentials : true
|
|
535
377
|
}
|
|
536
|
-
}
|
|
537
|
-
```
|
|
538
|
-
|
|
539
|
-
<div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
|
|
540
|
-
|
|
541
|
-
- ### Database Operations
|
|
542
|
-
|
|
543
|
-
```typescript
|
|
544
|
-
import { server, table, integer, text, primaryKey, notNull } from '@je-es/server';
|
|
545
|
-
|
|
546
|
-
const users = table('users', [
|
|
547
|
-
primaryKey(integer('id'), true),
|
|
548
|
-
notNull(text('name')),
|
|
549
|
-
notNull(text('email')),
|
|
550
|
-
integer('age')
|
|
551
|
-
]);
|
|
552
|
-
|
|
553
|
-
const app = server({
|
|
554
|
-
database: {
|
|
555
|
-
connection: './app.db',
|
|
556
|
-
schema: { users }
|
|
557
|
-
},
|
|
558
|
-
routes : [
|
|
559
|
-
// Get all records
|
|
560
|
-
{
|
|
561
|
-
method : 'GET',
|
|
562
|
-
path : '/users',
|
|
563
|
-
handler : (c) => {
|
|
564
|
-
const allUsers = c.db.all('users');
|
|
565
|
-
return c.json(allUsers);
|
|
566
|
-
}
|
|
567
|
-
},
|
|
568
|
-
|
|
569
|
-
// Find by ID
|
|
570
|
-
{
|
|
571
|
-
method : 'GET',
|
|
572
|
-
path : '/users/:id',
|
|
573
|
-
handler : (c) => {
|
|
574
|
-
const user = c.db.findById('users', parseInt(c.params.id));
|
|
575
|
-
if (!user) return c.status(404).json({ error: 'Not found' });
|
|
576
|
-
return c.json(user);
|
|
577
|
-
}
|
|
578
|
-
},
|
|
579
|
-
|
|
580
|
-
// Find with conditions
|
|
581
|
-
{
|
|
582
|
-
method : 'GET',
|
|
583
|
-
path : '/users/search',
|
|
584
|
-
handler : (c) => {
|
|
585
|
-
const users = c.db.find('users', {
|
|
586
|
-
name: c.query.name
|
|
587
|
-
});
|
|
588
|
-
return c.json(users);
|
|
589
|
-
}
|
|
590
|
-
},
|
|
591
|
-
|
|
592
|
-
// Insert
|
|
593
|
-
{
|
|
594
|
-
method : 'POST',
|
|
595
|
-
path : '/users',
|
|
596
|
-
handler : (c) => {
|
|
597
|
-
const newUser = c.db.insert('users', c.body);
|
|
598
|
-
return c.json(newUser);
|
|
599
|
-
}
|
|
600
|
-
},
|
|
601
|
-
|
|
602
|
-
// Update
|
|
603
|
-
{
|
|
604
|
-
method : 'PUT',
|
|
605
|
-
path : '/users/:id',
|
|
606
|
-
handler : (c) => {
|
|
607
|
-
const updated = c.db.update(
|
|
608
|
-
'users',
|
|
609
|
-
parseInt(c.params.id),
|
|
610
|
-
c.body
|
|
611
|
-
);
|
|
612
|
-
if (!updated) return c.status(404).json({ error: 'Not found' });
|
|
613
|
-
return c.json(updated);
|
|
614
|
-
}
|
|
615
|
-
},
|
|
616
|
-
|
|
617
|
-
// Delete
|
|
618
|
-
{
|
|
619
|
-
method : 'DELETE',
|
|
620
|
-
path : '/users/:id',
|
|
621
|
-
handler : (c) => {
|
|
622
|
-
c.db.delete('users', parseInt(c.params.id));
|
|
623
|
-
return c.json({ deleted: true });
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
]
|
|
627
|
-
});
|
|
378
|
+
}
|
|
628
379
|
```
|
|
629
380
|
|
|
630
|
-
|
|
381
|
+
<div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
|
|
631
382
|
|
|
632
|
-
- ###
|
|
383
|
+
- ### Input Sanitization
|
|
633
384
|
|
|
634
385
|
```typescript
|
|
635
|
-
|
|
636
|
-
database: {
|
|
637
|
-
connection: './app.db',
|
|
638
|
-
schema: { users }
|
|
639
|
-
},
|
|
640
|
-
routes : [
|
|
641
|
-
{
|
|
642
|
-
method : 'GET',
|
|
643
|
-
path : '/advanced-search',
|
|
644
|
-
handler : (c) => {
|
|
645
|
-
// Complex queries with query builder
|
|
646
|
-
const results = c.db.query()
|
|
647
|
-
.select(['name', 'email', 'age'])
|
|
648
|
-
.from('users')
|
|
649
|
-
.where({
|
|
650
|
-
column: 'age',
|
|
651
|
-
operator: '>=',
|
|
652
|
-
value: 18
|
|
653
|
-
})
|
|
654
|
-
.and({
|
|
655
|
-
column: 'name',
|
|
656
|
-
operator: 'LIKE',
|
|
657
|
-
value: '%John%'
|
|
658
|
-
})
|
|
659
|
-
.orderBy('age', 'DESC')
|
|
660
|
-
.limit(10)
|
|
661
|
-
.offset(0)
|
|
662
|
-
.execute();
|
|
663
|
-
|
|
664
|
-
return c.json(results);
|
|
665
|
-
}
|
|
666
|
-
},
|
|
667
|
-
|
|
668
|
-
// Multiple where conditions
|
|
669
|
-
{
|
|
670
|
-
method : 'GET',
|
|
671
|
-
path : '/filter',
|
|
672
|
-
handler : (c) => {
|
|
673
|
-
const users = c.db.query()
|
|
674
|
-
.select()
|
|
675
|
-
.from('users')
|
|
676
|
-
.where([
|
|
677
|
-
{ column: 'age', operator: '>', value: 25 },
|
|
678
|
-
{ column: 'age', operator: '<', value: 50 }
|
|
679
|
-
])
|
|
680
|
-
.execute();
|
|
681
|
-
|
|
682
|
-
return c.json(users);
|
|
683
|
-
}
|
|
684
|
-
},
|
|
685
|
-
|
|
686
|
-
// OR conditions
|
|
687
|
-
{
|
|
688
|
-
method : 'GET',
|
|
689
|
-
path : '/or-search',
|
|
690
|
-
handler : (c) => {
|
|
691
|
-
const users = c.db.query()
|
|
692
|
-
.select()
|
|
693
|
-
.from('users')
|
|
694
|
-
.where({ column: 'name', operator: '=', value: 'John' })
|
|
695
|
-
.or({ column: 'name', operator: '=', value: 'Jane' })
|
|
696
|
-
.execute();
|
|
697
|
-
|
|
698
|
-
return c.json(users);
|
|
699
|
-
}
|
|
700
|
-
},
|
|
386
|
+
import { SecurityManager } from '@je-es/server';
|
|
701
387
|
|
|
702
|
-
|
|
703
|
-
{
|
|
704
|
-
method : 'GET',
|
|
705
|
-
path : '/first-user',
|
|
706
|
-
handler : (c) => {
|
|
707
|
-
const user = c.db.query()
|
|
708
|
-
.select()
|
|
709
|
-
.from('users')
|
|
710
|
-
.limit(1)
|
|
711
|
-
.executeOne();
|
|
388
|
+
const security = new SecurityManager();
|
|
712
389
|
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
}
|
|
716
|
-
]
|
|
717
|
-
});
|
|
390
|
+
security.sanitizeHtml('xss');
|
|
391
|
+
security.sanitizeSql("'; DROP TABLE users--");
|
|
718
392
|
```
|
|
719
393
|
|
|
720
|
-
|
|
394
|
+
<div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
|
|
721
395
|
|
|
722
|
-
- ###
|
|
396
|
+
- ### CSRF Protection
|
|
723
397
|
|
|
724
398
|
```typescript
|
|
725
|
-
const
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
schema: { users, orders }
|
|
729
|
-
},
|
|
730
|
-
routes : [
|
|
731
|
-
{
|
|
732
|
-
method : 'POST',
|
|
733
|
-
path : '/place-order',
|
|
734
|
-
handler : (c) => {
|
|
735
|
-
try {
|
|
736
|
-
c.db.transaction((db) => {
|
|
737
|
-
// Insert order
|
|
738
|
-
const order = db.insert('orders', {
|
|
739
|
-
product_id: c.body.productId,
|
|
740
|
-
quantity: c.body.quantity
|
|
741
|
-
});
|
|
742
|
-
|
|
743
|
-
// Update product stock
|
|
744
|
-
const product = db.findById('products', c.body.productId);
|
|
745
|
-
db.update('products', c.body.productId, {
|
|
746
|
-
stock: product.stock - c.body.quantity
|
|
747
|
-
});
|
|
748
|
-
});
|
|
749
|
-
|
|
750
|
-
return c.json({ success: true });
|
|
751
|
-
} catch (error) {
|
|
752
|
-
return c.status(500).json({
|
|
753
|
-
error: 'Transaction failed'
|
|
754
|
-
});
|
|
755
|
-
}
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
]
|
|
759
|
-
});
|
|
399
|
+
const security = new SecurityManager();
|
|
400
|
+
const token = security.generateCsrfToken('session-id');
|
|
401
|
+
const valid = security.validateCsrfToken(token, 'session-id');
|
|
760
402
|
```
|
|
761
403
|
|
|
762
|
-
<
|
|
763
|
-
|
|
764
|
-
- ### Raw SQL
|
|
765
|
-
|
|
766
|
-
```typescript
|
|
767
|
-
const app = server({
|
|
768
|
-
database: {
|
|
769
|
-
connection: './app.db',
|
|
770
|
-
schema: { users }
|
|
771
|
-
},
|
|
772
|
-
routes : [
|
|
773
|
-
{
|
|
774
|
-
method : 'GET',
|
|
775
|
-
path : '/custom-query',
|
|
776
|
-
handler : (c) => {
|
|
777
|
-
// Execute raw SQL
|
|
778
|
-
const results = c.db.raw(
|
|
779
|
-
'SELECT * FROM users WHERE age > ? AND name LIKE ?',
|
|
780
|
-
[25, '%John%']
|
|
781
|
-
);
|
|
782
|
-
|
|
783
|
-
return c.json(results);
|
|
784
|
-
}
|
|
785
|
-
},
|
|
404
|
+
<br>
|
|
786
405
|
|
|
787
|
-
|
|
788
|
-
method : 'GET',
|
|
789
|
-
path : '/single-result',
|
|
790
|
-
handler : (c) => {
|
|
791
|
-
// Get single row
|
|
792
|
-
const user = c.db.rawOne(
|
|
793
|
-
'SELECT * FROM users WHERE id = ?',
|
|
794
|
-
[1]
|
|
795
|
-
);
|
|
406
|
+
- ## Error Handling
|
|
796
407
|
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
408
|
+
```typescript
|
|
409
|
+
import {
|
|
410
|
+
AppError, // Custom errors
|
|
411
|
+
ValidationError, // 400
|
|
412
|
+
DatabaseError, // 500
|
|
413
|
+
TimeoutError, // 408
|
|
414
|
+
RateLimitError // 429
|
|
415
|
+
} from '@je-es/server';
|
|
800
416
|
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
// Execute without return
|
|
806
|
-
c.db.exec('DELETE FROM users WHERE age < 18');
|
|
417
|
+
handler : (c) => {
|
|
418
|
+
if (!c.body?.email) {
|
|
419
|
+
throw new ValidationError('Email required');
|
|
420
|
+
}
|
|
807
421
|
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
]
|
|
812
|
-
});
|
|
813
|
-
```
|
|
422
|
+
if (invalid) {
|
|
423
|
+
throw new AppError('Invalid data', 400, 'INVALID_DATA');
|
|
424
|
+
}
|
|
814
425
|
|
|
815
|
-
|
|
426
|
+
return c.json({ success: true });
|
|
427
|
+
}
|
|
428
|
+
```
|
|
816
429
|
|
|
817
|
-
|
|
430
|
+
<br>
|
|
818
431
|
|
|
819
|
-
|
|
432
|
+
- ## Built-in Endpoints
|
|
820
433
|
|
|
821
|
-
|
|
822
|
-
|
|
434
|
+
```typescript
|
|
435
|
+
// Health check
|
|
436
|
+
GET /health
|
|
437
|
+
// Response: { status, timestamp, uptime, activeRequests }
|
|
823
438
|
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
pretty: true // Pretty print for development
|
|
829
|
-
}
|
|
830
|
-
});
|
|
439
|
+
// Readiness check
|
|
440
|
+
GET /readiness
|
|
441
|
+
// Response: { ready, checks: { database, activeRequests }, timestamp }
|
|
442
|
+
```
|
|
831
443
|
|
|
832
|
-
|
|
833
|
-
const route = {
|
|
834
|
-
method : 'GET',
|
|
835
|
-
path : '/test',
|
|
836
|
-
handler : (c) => {
|
|
837
|
-
c.logger?.info({ userId: 123 }, 'User accessed endpoint');
|
|
838
|
-
c.logger?.warn({ attempt: 3 }, 'Suspicious activity');
|
|
839
|
-
c.logger?.error({ error: 'DB connection failed' }, 'Database error');
|
|
840
|
-
return c.json({ ok: true });
|
|
841
|
-
}
|
|
842
|
-
};
|
|
843
|
-
```
|
|
444
|
+
<br>
|
|
844
445
|
|
|
845
|
-
|
|
446
|
+
- ## Advanced
|
|
846
447
|
|
|
847
448
|
- ### Cookie Management
|
|
848
449
|
|
|
849
450
|
```typescript
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
c.setCookie('session', 'user-token-123', {
|
|
858
|
-
maxAge: 3600, // 1 hour
|
|
859
|
-
expires: new Date('2025-12-31'),
|
|
860
|
-
path : '/',
|
|
861
|
-
domain: 'example.com',
|
|
862
|
-
secure: true, // HTTPS only
|
|
863
|
-
httpOnly: true, // No JavaScript access
|
|
864
|
-
sameSite: 'Strict' // CSRF protection
|
|
865
|
-
});
|
|
866
|
-
|
|
867
|
-
return c.json({ loggedIn: true });
|
|
868
|
-
}
|
|
869
|
-
},
|
|
870
|
-
{
|
|
871
|
-
method : 'GET',
|
|
872
|
-
path : '/profile',
|
|
873
|
-
handler : (c) => {
|
|
874
|
-
const session = c.getCookie('session');
|
|
875
|
-
if (!session) {
|
|
876
|
-
return c.status(401).json({ error: 'Unauthorized' });
|
|
877
|
-
}
|
|
878
|
-
return c.json({ session });
|
|
879
|
-
}
|
|
880
|
-
},
|
|
881
|
-
{
|
|
882
|
-
method : 'POST',
|
|
883
|
-
path : '/logout',
|
|
884
|
-
handler : (c) => {
|
|
885
|
-
c.deleteCookie('session');
|
|
886
|
-
return c.json({ loggedOut: true });
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
]
|
|
890
|
-
});
|
|
891
|
-
```
|
|
892
|
-
|
|
893
|
-
<div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
|
|
894
|
-
|
|
895
|
-
- ### Dynamic Routing
|
|
896
|
-
|
|
897
|
-
```typescript
|
|
898
|
-
const app = server({
|
|
899
|
-
routes : [
|
|
900
|
-
// Simple parameter
|
|
901
|
-
{
|
|
902
|
-
method : 'GET',
|
|
903
|
-
path : '/users/:id',
|
|
904
|
-
handler : (c) => c.json({ userId: c.params.id })
|
|
905
|
-
},
|
|
906
|
-
|
|
907
|
-
// Multiple parameters
|
|
908
|
-
{
|
|
909
|
-
method : 'GET',
|
|
910
|
-
path : '/posts/:postId/comments/:commentId',
|
|
911
|
-
handler : (c) => c.json({
|
|
912
|
-
postId: c.params.postId,
|
|
913
|
-
commentId: c.params.commentId
|
|
914
|
-
})
|
|
915
|
-
},
|
|
916
|
-
|
|
917
|
-
// Complex patterns
|
|
918
|
-
{
|
|
919
|
-
method : 'GET',
|
|
920
|
-
path : '/api/:version/:resource',
|
|
921
|
-
handler : (c) => c.json({
|
|
922
|
-
version: c.params.version,
|
|
923
|
-
resource: c.params.resource
|
|
924
|
-
})
|
|
925
|
-
}
|
|
926
|
-
]
|
|
451
|
+
c.setCookie('session', 'token', {
|
|
452
|
+
maxAge : 3600,
|
|
453
|
+
httpOnly : true,
|
|
454
|
+
secure : true,
|
|
455
|
+
sameSite : 'Strict',
|
|
456
|
+
path : '/',
|
|
457
|
+
domain : 'example.com'
|
|
927
458
|
});
|
|
928
459
|
```
|
|
929
460
|
|
|
930
|
-
|
|
461
|
+
<div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
|
|
931
462
|
|
|
932
|
-
- ###
|
|
463
|
+
- ### Static File Options
|
|
933
464
|
|
|
934
465
|
```typescript
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
{
|
|
949
|
-
ready: true,
|
|
950
|
-
checks: {
|
|
951
|
-
database: 'connected', // or 'not configured'
|
|
952
|
-
activeRequests: 5
|
|
953
|
-
},
|
|
954
|
-
timestamp: '2025-11-28T10:00:00.000Z'
|
|
466
|
+
static: {
|
|
467
|
+
path : '/public',
|
|
468
|
+
directory : './public',
|
|
469
|
+
maxAge : 3600,
|
|
470
|
+
index : ['index.html'],
|
|
471
|
+
dotfiles : 'deny', // 'allow' | 'deny' | 'ignore'
|
|
472
|
+
etag : true,
|
|
473
|
+
lastModified : true,
|
|
474
|
+
immutable : false,
|
|
475
|
+
extensions : ['html', 'htm'],
|
|
476
|
+
setHeaders : (ctx, path) => {
|
|
477
|
+
ctx.setHeader('X-Custom', 'value');
|
|
478
|
+
}
|
|
955
479
|
}
|
|
956
480
|
```
|
|
957
481
|
|
|
958
|
-
|
|
482
|
+
<div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
|
|
959
483
|
|
|
960
484
|
- ### Graceful Shutdown
|
|
961
485
|
|
|
962
486
|
```typescript
|
|
963
487
|
const app = server({
|
|
964
|
-
gracefulShutdownTimeout: 10000,
|
|
488
|
+
gracefulShutdownTimeout: 10000,
|
|
965
489
|
onShutdown: async () => {
|
|
966
|
-
|
|
967
|
-
// Close external connections, flush logs, etc.
|
|
490
|
+
// Cleanup
|
|
968
491
|
}
|
|
969
492
|
});
|
|
970
493
|
|
|
971
|
-
await app.start();
|
|
972
|
-
|
|
973
|
-
// Handle signals
|
|
974
494
|
process.on('SIGTERM', async () => {
|
|
975
495
|
await app.stop();
|
|
976
496
|
process.exit(0);
|
|
977
497
|
});
|
|
978
|
-
|
|
979
|
-
process.on('SIGINT', async () => {
|
|
980
|
-
await app.stop();
|
|
981
|
-
process.exit(0);
|
|
982
|
-
});
|
|
983
498
|
```
|
|
984
499
|
|
|
985
|
-
|
|
500
|
+
<div align="center"> <img src="./assets/img/line.png" alt="line" style="display: block; margin-top:20px;margin-bottom:20px;width:500px;"/> <br> </div>
|
|
986
501
|
|
|
987
|
-
- ###
|
|
502
|
+
- ### Logging
|
|
988
503
|
|
|
989
504
|
```typescript
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
handler : (c) => c.json({ route: 'initial' })
|
|
996
|
-
}
|
|
997
|
-
]
|
|
998
|
-
});
|
|
999
|
-
|
|
1000
|
-
await app.start();
|
|
1001
|
-
|
|
1002
|
-
// Add routes after server starts
|
|
1003
|
-
app.addRoute({
|
|
1004
|
-
method : 'POST',
|
|
1005
|
-
path : '/dynamic',
|
|
1006
|
-
handler : (c) => c.json({ route: 'dynamic', body: c.body })
|
|
1007
|
-
});
|
|
1008
|
-
|
|
1009
|
-
// Get all registered routes
|
|
1010
|
-
const routes = app.getRoutes();
|
|
1011
|
-
console.log(routes);
|
|
505
|
+
handler : (c) => {
|
|
506
|
+
c.logger?.info( { userId: 123 }, 'User action');
|
|
507
|
+
c.logger?.warn( { attempt: 3 }, 'Warning');
|
|
508
|
+
c.logger?.error({ error: 'msg' }, 'Error');
|
|
509
|
+
}
|
|
1012
510
|
```
|
|
1013
511
|
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
512
|
+
<br>
|
|
513
|
+
|
|
514
|
+
- ## Complete Example
|
|
515
|
+
|
|
516
|
+
```typescript
|
|
517
|
+
import { server, table, integer, text, primaryKey, notNull } from '@je-es/server';
|
|
518
|
+
|
|
519
|
+
const users = table('users', [
|
|
520
|
+
primaryKey(integer('id'), true),
|
|
521
|
+
notNull(text('name')),
|
|
522
|
+
notNull(text('email'))
|
|
523
|
+
]);
|
|
524
|
+
|
|
525
|
+
const app = server({
|
|
526
|
+
port : 3000,
|
|
527
|
+
logging : { level: 'info', pretty: true },
|
|
528
|
+
database : { connection: './app.db', schema: { users } },
|
|
529
|
+
security : {
|
|
530
|
+
rateLimit : { max: 100, windowMs: 60000 },
|
|
531
|
+
cors : { origin: ['http://localhost:3000'] }
|
|
532
|
+
},
|
|
533
|
+
static : { path: '/public', directory: './public' },
|
|
534
|
+
routes : [
|
|
535
|
+
{
|
|
536
|
+
method : 'GET',
|
|
537
|
+
path : '/api/users',
|
|
538
|
+
handler : (c) => c.json(c.db!.all('users'))
|
|
539
|
+
},
|
|
540
|
+
{
|
|
541
|
+
method : 'POST',
|
|
542
|
+
path : '/api/users',
|
|
543
|
+
handler : (c) => {
|
|
544
|
+
if (!c.body?.name || !c.body?.email) {
|
|
545
|
+
throw new ValidationError('Name and email required');
|
|
1029
546
|
}
|
|
547
|
+
const user = c.db!.insert('users', c.body);
|
|
548
|
+
return c.status(201).json(user);
|
|
1030
549
|
}
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
```typescript
|
|
1040
|
-
import { AppError, ValidationError } from '@je-es/server';
|
|
1041
|
-
|
|
1042
|
-
const app = server({
|
|
1043
|
-
routes : [
|
|
1044
|
-
{
|
|
1045
|
-
method : 'POST',
|
|
1046
|
-
path : '/validate',
|
|
1047
|
-
handler : (c) => {
|
|
1048
|
-
if (!c.body?.email) {
|
|
1049
|
-
throw new ValidationError('Email is required');
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
|
-
if (!c.body.email.includes('@')) {
|
|
1053
|
-
throw new AppError('Invalid email format', 400, 'INVALID_EMAIL');
|
|
1054
|
-
}
|
|
1055
|
-
|
|
1056
|
-
return c.json({ valid: true });
|
|
1057
|
-
}
|
|
550
|
+
},
|
|
551
|
+
{
|
|
552
|
+
method : 'GET',
|
|
553
|
+
path : '/api/users/:id',
|
|
554
|
+
handler : (c) => {
|
|
555
|
+
const user = c.db!.findById('users', parseInt(c.params.id));
|
|
556
|
+
return user ? c.json(user) : c.status(404).json({ error: 'Not found' });
|
|
1058
557
|
}
|
|
1059
|
-
|
|
1060
|
-
|
|
558
|
+
}
|
|
559
|
+
]
|
|
560
|
+
});
|
|
1061
561
|
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
error : 'Email is required',
|
|
1065
|
-
code : 'VALIDATION_ERROR',
|
|
1066
|
-
requestId : 'unique-request-id'
|
|
1067
|
-
}
|
|
1068
|
-
```
|
|
562
|
+
await app.start();
|
|
563
|
+
```
|
|
1069
564
|
|
|
1070
565
|
<!-- ╚═════════════════════════════════════════════════════════════════╝ -->
|
|
1071
566
|
|