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