create-tinny-backend 1.0.0
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/index.js +107 -0
- package/package.json +38 -0
- package/template/admin/auth.ts +85 -0
- package/template/admin/monitor.ts +102 -0
- package/template/admin/routes.ts +192 -0
- package/template/app/index.ts +6 -0
- package/template/lib/CreateSrv.ts +9 -0
- package/template/package.json +48 -0
- package/template/public/admin/index.html +2012 -0
- package/template/public/doc/index.html +1651 -0
- package/template/public/example/default.html +437 -0
- package/template/public/imgs/logo-square.png +0 -0
- package/template/public/imgs/logo.png +0 -0
- package/template/public/imgs/no-bg-logo.png +0 -0
- package/template/public/login/css/style.css +109 -0
- package/template/public/login/index.html +883 -0
- package/template/public/login/new-password.html +1173 -0
- package/template/public/status/401.html +649 -0
- package/template/public/status/404.html +668 -0
- package/template/tsconfig.json +44 -0
|
@@ -0,0 +1,1651 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en" data-theme="light">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
|
6
|
+
<title>TinnyBackend · Complete Documentation</title>
|
|
7
|
+
<link rel="shortcut icon" href="/imgs/logo.png" type="image/x-icon">
|
|
8
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
|
|
9
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
10
|
+
|
|
11
|
+
<!-- Highlight.js CSS -->
|
|
12
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css" id="hljs-light">
|
|
13
|
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css" id="hljs-dark" disabled>
|
|
14
|
+
|
|
15
|
+
<style>
|
|
16
|
+
:root {
|
|
17
|
+
--bg: #f4f6fa;
|
|
18
|
+
--text: #1e293b;
|
|
19
|
+
--text-secondary: #475569;
|
|
20
|
+
--text-muted: #64748b;
|
|
21
|
+
--border: #e2e8f0;
|
|
22
|
+
--card-bg: #ffffff;
|
|
23
|
+
--card-border: #e9eef3;
|
|
24
|
+
--sidebar-bg: #f8fafc;
|
|
25
|
+
--input-focus: #2563eb;
|
|
26
|
+
--heading: #0f172a;
|
|
27
|
+
--shadow-sm: 0 1px 3px rgba(0,0,0,0.06);
|
|
28
|
+
--shadow-md: 0 4px 12px rgba(0,0,0,0.08);
|
|
29
|
+
--shadow-lg: 0 12px 40px rgba(0,0,0,0.1);
|
|
30
|
+
--toggle-bg: #f1f5f9;
|
|
31
|
+
--badge-bg: #dbeafe;
|
|
32
|
+
--badge-text: #1e40af;
|
|
33
|
+
--code-bg: #f1f5f9;
|
|
34
|
+
--code-border: #dce3eb;
|
|
35
|
+
--details-bg: #f9fafc;
|
|
36
|
+
--nav-text: #334155;
|
|
37
|
+
--nav-hover-bg: #eef2ff;
|
|
38
|
+
--nav-hover-text: #1e3a8a;
|
|
39
|
+
--active-bg: #2563eb;
|
|
40
|
+
--active-text: #ffffff;
|
|
41
|
+
--table-stripe: #f8fafc;
|
|
42
|
+
--table-border: #e2e8f0;
|
|
43
|
+
--scrollbar-track: #e2e8f0;
|
|
44
|
+
--scrollbar-thumb: #94a3b8;
|
|
45
|
+
--code-color: #1e293b;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
[data-theme="dark"] {
|
|
49
|
+
--bg: #1a1a1a;
|
|
50
|
+
--text: #d4d4d4;
|
|
51
|
+
--text-secondary: #a0a0a0;
|
|
52
|
+
--text-muted: #808080;
|
|
53
|
+
--border: #3a3a3a;
|
|
54
|
+
--card-bg: #2a2a2a;
|
|
55
|
+
--card-border: #3a3a3a;
|
|
56
|
+
--sidebar-bg: #222222;
|
|
57
|
+
--input-focus: #60a5fa;
|
|
58
|
+
--heading: #e0e0e0;
|
|
59
|
+
--shadow-sm: 0 1px 3px rgba(0,0,0,0.3);
|
|
60
|
+
--shadow-md: 0 4px 12px rgba(0,0,0,0.4);
|
|
61
|
+
--shadow-lg: 0 12px 40px rgba(0,0,0,0.5);
|
|
62
|
+
--toggle-bg: #333333;
|
|
63
|
+
--badge-bg: #2a2a2a;
|
|
64
|
+
--badge-text: #a0a0a0;
|
|
65
|
+
--code-bg: #1e1e1e;
|
|
66
|
+
--code-border: #3a3a3a;
|
|
67
|
+
--details-bg: #2a2a2a;
|
|
68
|
+
--nav-text: #b0b0b0;
|
|
69
|
+
--nav-hover-bg: #333333;
|
|
70
|
+
--nav-hover-text: #d4d4d4;
|
|
71
|
+
--active-bg: #404040;
|
|
72
|
+
--active-text: #ffffff;
|
|
73
|
+
--table-stripe: #2a2a2a;
|
|
74
|
+
--table-border: #3a3a3a;
|
|
75
|
+
--scrollbar-track: #2a2a2a;
|
|
76
|
+
--scrollbar-thumb: #4a4a4a;
|
|
77
|
+
--code-color: #d4d4d4;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
* {
|
|
81
|
+
margin: 0;
|
|
82
|
+
padding: 0;
|
|
83
|
+
box-sizing: border-box;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
body {
|
|
87
|
+
background: var(--bg);
|
|
88
|
+
font-family: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
|
|
89
|
+
min-height: 100vh;
|
|
90
|
+
color: var(--text);
|
|
91
|
+
transition: background 0.3s ease, color 0.3s ease;
|
|
92
|
+
display: flex;
|
|
93
|
+
line-height: 1.6;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/* ===== SIDEBAR ===== */
|
|
97
|
+
.sidebar {
|
|
98
|
+
width: 280px;
|
|
99
|
+
min-width: 280px;
|
|
100
|
+
height: 100vh;
|
|
101
|
+
position: fixed;
|
|
102
|
+
left: 0;
|
|
103
|
+
top: 0;
|
|
104
|
+
background: var(--sidebar-bg);
|
|
105
|
+
border-right: 1px solid var(--border);
|
|
106
|
+
display: flex;
|
|
107
|
+
flex-direction: column;
|
|
108
|
+
padding: 1.5rem 1rem;
|
|
109
|
+
gap: 0.3rem;
|
|
110
|
+
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), background 0.3s ease, border-color 0.3s ease;
|
|
111
|
+
z-index: 100;
|
|
112
|
+
overflow-y: auto;
|
|
113
|
+
box-shadow: var(--shadow-sm);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.sidebar::-webkit-scrollbar {
|
|
117
|
+
width: 4px;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.sidebar::-webkit-scrollbar-track {
|
|
121
|
+
background: var(--scrollbar-track);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.sidebar::-webkit-scrollbar-thumb {
|
|
125
|
+
background: var(--scrollbar-thumb);
|
|
126
|
+
border-radius: 10px;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.sidebar.collapsed {
|
|
130
|
+
transform: translateX(-100%);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.sidebar-logo {
|
|
134
|
+
display: flex;
|
|
135
|
+
align-items: center;
|
|
136
|
+
gap: 0.6rem;
|
|
137
|
+
margin-bottom: 1.5rem;
|
|
138
|
+
padding: 0 0.5rem;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.sidebar-logo img {
|
|
142
|
+
width: 36px;
|
|
143
|
+
height: 36px;
|
|
144
|
+
border-radius: 10px;
|
|
145
|
+
object-fit: contain;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.sidebar-logo span {
|
|
149
|
+
font-weight: 700;
|
|
150
|
+
font-size: 1.3rem;
|
|
151
|
+
color: var(--heading);
|
|
152
|
+
}
|
|
153
|
+
.sidebar-logo span strong {
|
|
154
|
+
color: var(--input-focus);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.sidebar-title {
|
|
158
|
+
font-size: 0.7rem;
|
|
159
|
+
text-transform: uppercase;
|
|
160
|
+
letter-spacing: 0.12em;
|
|
161
|
+
color: var(--text-muted);
|
|
162
|
+
margin: 0.5rem 0 0.5rem;
|
|
163
|
+
padding-left: 0.7rem;
|
|
164
|
+
font-weight: 700;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.nav-item {
|
|
168
|
+
display: flex;
|
|
169
|
+
align-items: center;
|
|
170
|
+
gap: 0.7rem;
|
|
171
|
+
padding: 0.7rem 1rem;
|
|
172
|
+
border-radius: 0.8rem;
|
|
173
|
+
color: var(--nav-text);
|
|
174
|
+
font-weight: 500;
|
|
175
|
+
transition: all 0.2s ease;
|
|
176
|
+
cursor: pointer;
|
|
177
|
+
background: transparent;
|
|
178
|
+
border: none;
|
|
179
|
+
width: 100%;
|
|
180
|
+
text-align: left;
|
|
181
|
+
font-size: 0.88rem;
|
|
182
|
+
font-family: 'Inter', sans-serif;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.nav-item i {
|
|
186
|
+
width: 20px;
|
|
187
|
+
font-size: 0.9rem;
|
|
188
|
+
color: var(--text-muted);
|
|
189
|
+
transition: color 0.2s ease;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.nav-item:hover {
|
|
193
|
+
background: var(--nav-hover-bg);
|
|
194
|
+
color: var(--nav-hover-text);
|
|
195
|
+
}
|
|
196
|
+
.nav-item:hover i {
|
|
197
|
+
color: var(--nav-hover-text);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.nav-item.active {
|
|
201
|
+
background: var(--active-bg);
|
|
202
|
+
color: var(--active-text);
|
|
203
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
|
204
|
+
}
|
|
205
|
+
.nav-item.active i {
|
|
206
|
+
color: var(--active-text);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.sidebar-footer {
|
|
210
|
+
margin-top: auto;
|
|
211
|
+
padding-top: 1rem;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.github-btn {
|
|
215
|
+
display: flex;
|
|
216
|
+
align-items: center;
|
|
217
|
+
justify-content: center;
|
|
218
|
+
gap: 0.6rem;
|
|
219
|
+
width: 100%;
|
|
220
|
+
padding: 0.75rem 1rem;
|
|
221
|
+
border-radius: 0.8rem;
|
|
222
|
+
background: var(--toggle-bg);
|
|
223
|
+
color: var(--text);
|
|
224
|
+
font-weight: 600;
|
|
225
|
+
font-size: 0.88rem;
|
|
226
|
+
text-decoration: none;
|
|
227
|
+
transition: all 0.2s ease;
|
|
228
|
+
border: 1px solid var(--border);
|
|
229
|
+
cursor: pointer;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.github-btn:hover {
|
|
233
|
+
background: var(--input-focus);
|
|
234
|
+
color: white;
|
|
235
|
+
border-color: var(--input-focus);
|
|
236
|
+
transform: translateY(-1px);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.github-btn i {
|
|
240
|
+
font-size: 1.1rem;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/* ===== MAIN CONTENT ===== */
|
|
244
|
+
.main-content {
|
|
245
|
+
flex: 1;
|
|
246
|
+
margin-left: 280px;
|
|
247
|
+
min-height: 100vh;
|
|
248
|
+
transition: margin-left 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
249
|
+
width: 100%;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.main-content.expanded {
|
|
253
|
+
margin-left: 0;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/* Top bar */
|
|
257
|
+
.top-bar {
|
|
258
|
+
background: var(--card-bg);
|
|
259
|
+
border-bottom: 1px solid var(--border);
|
|
260
|
+
padding: 0.9rem 2rem;
|
|
261
|
+
display: flex;
|
|
262
|
+
align-items: center;
|
|
263
|
+
justify-content: space-between;
|
|
264
|
+
position: sticky;
|
|
265
|
+
top: 0;
|
|
266
|
+
z-index: 50;
|
|
267
|
+
transition: background 0.3s ease, border-color 0.3s ease;
|
|
268
|
+
box-shadow: var(--shadow-sm);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.top-bar-left {
|
|
272
|
+
display: flex;
|
|
273
|
+
align-items: center;
|
|
274
|
+
gap: 1rem;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.sidebar-toggle {
|
|
278
|
+
background: var(--toggle-bg);
|
|
279
|
+
border: 1px solid var(--border);
|
|
280
|
+
color: var(--text);
|
|
281
|
+
width: 36px;
|
|
282
|
+
height: 36px;
|
|
283
|
+
border-radius: 0.6rem;
|
|
284
|
+
display: flex;
|
|
285
|
+
align-items: center;
|
|
286
|
+
justify-content: center;
|
|
287
|
+
cursor: pointer;
|
|
288
|
+
font-size: 1rem;
|
|
289
|
+
transition: all 0.2s ease;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
.sidebar-toggle:hover {
|
|
293
|
+
background: var(--input-focus);
|
|
294
|
+
color: white;
|
|
295
|
+
border-color: var(--input-focus);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.version-badge {
|
|
299
|
+
background: var(--toggle-bg);
|
|
300
|
+
padding: 0.3rem 0.8rem;
|
|
301
|
+
border-radius: 2rem;
|
|
302
|
+
font-size: 0.78rem;
|
|
303
|
+
font-weight: 600;
|
|
304
|
+
color: var(--text);
|
|
305
|
+
display: flex;
|
|
306
|
+
align-items: center;
|
|
307
|
+
gap: 0.3rem;
|
|
308
|
+
border: 1px solid var(--border);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.theme-toggle {
|
|
312
|
+
background: var(--toggle-bg);
|
|
313
|
+
border: 1px solid var(--border);
|
|
314
|
+
color: var(--text);
|
|
315
|
+
width: 36px;
|
|
316
|
+
height: 36px;
|
|
317
|
+
border-radius: 0.6rem;
|
|
318
|
+
display: flex;
|
|
319
|
+
align-items: center;
|
|
320
|
+
justify-content: center;
|
|
321
|
+
cursor: pointer;
|
|
322
|
+
font-size: 1rem;
|
|
323
|
+
transition: all 0.2s ease;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
.theme-toggle:hover {
|
|
327
|
+
background: var(--input-focus);
|
|
328
|
+
color: white;
|
|
329
|
+
border-color: var(--input-focus);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/* Content */
|
|
333
|
+
.content {
|
|
334
|
+
padding: 2.5rem 3rem;
|
|
335
|
+
max-width: 1100px;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.section {
|
|
339
|
+
display: block;
|
|
340
|
+
animation: fadeIn 0.3s ease;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
@keyframes fadeIn {
|
|
344
|
+
from { opacity: 0; transform: translateY(8px); }
|
|
345
|
+
to { opacity: 1; transform: translateY(0); }
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
.hidden-section {
|
|
349
|
+
display: none;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
h2 {
|
|
353
|
+
font-size: 1.9rem;
|
|
354
|
+
font-weight: 700;
|
|
355
|
+
color: var(--heading);
|
|
356
|
+
margin-bottom: 0.4rem;
|
|
357
|
+
display: flex;
|
|
358
|
+
align-items: center;
|
|
359
|
+
gap: 0.6rem;
|
|
360
|
+
letter-spacing: -0.3px;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
h2 i {
|
|
364
|
+
color: var(--input-focus);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
h3 {
|
|
368
|
+
font-size: 1.3rem;
|
|
369
|
+
font-weight: 650;
|
|
370
|
+
color: var(--heading);
|
|
371
|
+
margin: 1.8rem 0 0.8rem;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
h4 {
|
|
375
|
+
font-size: 1.05rem;
|
|
376
|
+
font-weight: 600;
|
|
377
|
+
color: var(--heading);
|
|
378
|
+
margin: 1.2rem 0 0.5rem;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
.subtitle {
|
|
382
|
+
color: var(--text-secondary);
|
|
383
|
+
margin-bottom: 1.5rem;
|
|
384
|
+
font-size: 1rem;
|
|
385
|
+
border-left: 3px solid var(--input-focus);
|
|
386
|
+
padding-left: 1rem;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
.card-grid {
|
|
390
|
+
display: grid;
|
|
391
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
392
|
+
gap: 1.2rem;
|
|
393
|
+
margin: 1.5rem 0 2rem;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
.stat-card {
|
|
397
|
+
background: var(--card-bg);
|
|
398
|
+
padding: 1.3rem;
|
|
399
|
+
border-radius: 1rem;
|
|
400
|
+
border: 1px solid var(--card-border);
|
|
401
|
+
transition: all 0.3s ease;
|
|
402
|
+
box-shadow: var(--shadow-sm);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
.stat-card:hover {
|
|
406
|
+
border-color: var(--input-focus);
|
|
407
|
+
box-shadow: var(--shadow-md);
|
|
408
|
+
transform: translateY(-2px);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
.stat-card i {
|
|
412
|
+
color: var(--input-focus);
|
|
413
|
+
font-size: 1.1rem;
|
|
414
|
+
margin-bottom: 0.3rem;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.stat-card strong {
|
|
418
|
+
display: block;
|
|
419
|
+
margin: 0.3rem 0 0.2rem;
|
|
420
|
+
font-size: 0.95rem;
|
|
421
|
+
color: var(--heading);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
.stat-card p {
|
|
425
|
+
font-size: 0.85rem;
|
|
426
|
+
color: var(--text-secondary);
|
|
427
|
+
margin: 0;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
details {
|
|
431
|
+
background: var(--details-bg);
|
|
432
|
+
border: 1px solid var(--border);
|
|
433
|
+
border-radius: 0.9rem;
|
|
434
|
+
padding: 0.2rem 1.2rem;
|
|
435
|
+
margin-bottom: 0.8rem;
|
|
436
|
+
transition: all 0.2s ease;
|
|
437
|
+
box-shadow: var(--shadow-sm);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
details[open] {
|
|
441
|
+
border-color: var(--input-focus);
|
|
442
|
+
box-shadow: var(--shadow-md);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
summary {
|
|
446
|
+
font-weight: 600;
|
|
447
|
+
font-size: 1rem;
|
|
448
|
+
padding: 0.9rem 0;
|
|
449
|
+
cursor: pointer;
|
|
450
|
+
display: flex;
|
|
451
|
+
align-items: center;
|
|
452
|
+
gap: 0.7rem;
|
|
453
|
+
color: var(--text);
|
|
454
|
+
list-style: none;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
summary::-webkit-details-marker {
|
|
458
|
+
display: none;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
summary i {
|
|
462
|
+
color: var(--input-focus);
|
|
463
|
+
font-size: 0.95rem;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/* ===== CODE BLOCK STYLES ===== */
|
|
467
|
+
.code-block {
|
|
468
|
+
background: var(--code-bg);
|
|
469
|
+
border-radius: 0.7rem;
|
|
470
|
+
padding: 1rem 1.2rem;
|
|
471
|
+
margin: 0.8rem 0;
|
|
472
|
+
border: 1px solid var(--code-border);
|
|
473
|
+
overflow-x: auto;
|
|
474
|
+
position: relative;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
.code-block .code-label {
|
|
478
|
+
position: absolute;
|
|
479
|
+
top: -10px;
|
|
480
|
+
right: 12px;
|
|
481
|
+
background: var(--card-bg);
|
|
482
|
+
padding: 0 0.5rem;
|
|
483
|
+
font-size: 0.6rem;
|
|
484
|
+
color: var(--text-muted);
|
|
485
|
+
font-family: 'Inter', sans-serif;
|
|
486
|
+
font-weight: 600;
|
|
487
|
+
text-transform: uppercase;
|
|
488
|
+
letter-spacing: 0.5px;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
.code-block pre {
|
|
492
|
+
margin: 0;
|
|
493
|
+
padding: 0;
|
|
494
|
+
background: transparent !important;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
.code-block code {
|
|
498
|
+
font-family: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace;
|
|
499
|
+
font-size: 0.82rem;
|
|
500
|
+
background: transparent !important;
|
|
501
|
+
padding: 0 !important;
|
|
502
|
+
color: var(--code-color);
|
|
503
|
+
border: none;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/* Highlight.js overrides for dark mode */
|
|
507
|
+
[data-theme="dark"] .hljs {
|
|
508
|
+
background: transparent !important;
|
|
509
|
+
color: #d4d4d4;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
[data-theme="dark"] .hljs-keyword,
|
|
513
|
+
[data-theme="dark"] .hljs-selector-tag,
|
|
514
|
+
[data-theme="dark"] .hljs-literal,
|
|
515
|
+
[data-theme="dark"] .hljs-section,
|
|
516
|
+
[data-theme="dark"] .hljs-link {
|
|
517
|
+
color: #569cd6;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
[data-theme="dark"] .hljs-function .hljs-keyword {
|
|
521
|
+
color: #c586c0;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
[data-theme="dark"] .hljs-string,
|
|
525
|
+
[data-theme="dark"] .hljs-title,
|
|
526
|
+
[data-theme="dark"] .hljs-name,
|
|
527
|
+
[data-theme="dark"] .hljs-type,
|
|
528
|
+
[data-theme="dark"] .hljs-attribute,
|
|
529
|
+
[data-theme="dark"] .hljs-symbol,
|
|
530
|
+
[data-theme="dark"] .hljs-bullet,
|
|
531
|
+
[data-theme="dark"] .hljs-built_in,
|
|
532
|
+
[data-theme="dark"] .hljs-addition,
|
|
533
|
+
[data-theme="dark"] .hljs-variable,
|
|
534
|
+
[data-theme="dark"] .hljs-template-tag,
|
|
535
|
+
[data-theme="dark"] .hljs-template-variable {
|
|
536
|
+
color: #ce9178;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
[data-theme="dark"] .hljs-comment,
|
|
540
|
+
[data-theme="dark"] .hljs-quote,
|
|
541
|
+
[data-theme="dark"] .hljs-deletion,
|
|
542
|
+
[data-theme="dark"] .hljs-meta {
|
|
543
|
+
color: #6a9955;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
[data-theme="dark"] .hljs-number {
|
|
547
|
+
color: #b5cea8;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/* ===== OTHER ELEMENTS ===== */
|
|
551
|
+
.badge {
|
|
552
|
+
background: var(--badge-bg);
|
|
553
|
+
color: var(--badge-text);
|
|
554
|
+
padding: 0.2rem 0.6rem;
|
|
555
|
+
border-radius: 30px;
|
|
556
|
+
font-size: 0.7rem;
|
|
557
|
+
font-weight: 700;
|
|
558
|
+
margin: 0 0.15rem;
|
|
559
|
+
display: inline-block;
|
|
560
|
+
border: 1px solid var(--border);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
.badge-method {
|
|
564
|
+
background: var(--input-focus);
|
|
565
|
+
color: white;
|
|
566
|
+
border: none;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
.badge-get { background: #48bb78; color: white; }
|
|
570
|
+
.badge-post { background: #ecc94b; color: #1a1a1a; }
|
|
571
|
+
.badge-put { background: #4299e1; color: white; }
|
|
572
|
+
.badge-delete { background: #fc8181; color: white; }
|
|
573
|
+
.badge-patch { background: #9f7aea; color: white; }
|
|
574
|
+
|
|
575
|
+
p {
|
|
576
|
+
margin: 0.5rem 0;
|
|
577
|
+
line-height: 1.7;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
code {
|
|
581
|
+
background: var(--code-bg);
|
|
582
|
+
padding: 0.1rem 0.4rem;
|
|
583
|
+
border-radius: 0.3rem;
|
|
584
|
+
font-family: 'JetBrains Mono', 'Fira Code', monospace;
|
|
585
|
+
font-size: 0.85em;
|
|
586
|
+
border: 1px solid var(--code-border);
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/* Tables */
|
|
590
|
+
.info-table {
|
|
591
|
+
width: 100%;
|
|
592
|
+
border-collapse: collapse;
|
|
593
|
+
margin: 1rem 0 1.5rem;
|
|
594
|
+
font-size: 0.9rem;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
.info-table th {
|
|
598
|
+
background: var(--sidebar-bg);
|
|
599
|
+
padding: 0.7rem 1rem;
|
|
600
|
+
text-align: left;
|
|
601
|
+
font-weight: 650;
|
|
602
|
+
color: var(--text);
|
|
603
|
+
border-bottom: 2px solid var(--border);
|
|
604
|
+
font-size: 0.8rem;
|
|
605
|
+
text-transform: uppercase;
|
|
606
|
+
letter-spacing: 0.05em;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
.info-table td {
|
|
610
|
+
padding: 0.65rem 1rem;
|
|
611
|
+
border-bottom: 1px solid var(--table-border);
|
|
612
|
+
color: var(--text-secondary);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
.info-table tr:nth-child(even) td {
|
|
616
|
+
background: var(--table-stripe);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
.info-table td:first-child {
|
|
620
|
+
font-weight: 600;
|
|
621
|
+
color: var(--text);
|
|
622
|
+
font-family: 'JetBrains Mono', 'Fira Code', monospace;
|
|
623
|
+
font-size: 0.85rem;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
ul, ol {
|
|
627
|
+
margin: 0.5rem 0 0.5rem 1.5rem;
|
|
628
|
+
color: var(--text-secondary);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
li {
|
|
632
|
+
margin: 0.3rem 0;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
strong {
|
|
636
|
+
color: var(--text);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
.note {
|
|
640
|
+
background: var(--details-bg);
|
|
641
|
+
border-left: 3px solid var(--input-focus);
|
|
642
|
+
padding: 0.8rem 1.2rem;
|
|
643
|
+
border-radius: 0 0.7rem 0.7rem 0;
|
|
644
|
+
margin: 1rem 0;
|
|
645
|
+
font-size: 0.9rem;
|
|
646
|
+
color: var(--text-secondary);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
.note.warning {
|
|
650
|
+
border-left-color: #ecc94b;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
.note.error {
|
|
654
|
+
border-left-color: #fc8181;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
/* Overlay */
|
|
658
|
+
.sidebar-overlay {
|
|
659
|
+
display: none;
|
|
660
|
+
position: fixed;
|
|
661
|
+
inset: 0;
|
|
662
|
+
background: rgba(0,0,0,0.5);
|
|
663
|
+
z-index: 99;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
.sidebar-overlay.show {
|
|
667
|
+
display: block;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
@media (max-width: 850px) {
|
|
671
|
+
.sidebar {
|
|
672
|
+
transform: translateX(-100%);
|
|
673
|
+
}
|
|
674
|
+
.sidebar.mobile-open {
|
|
675
|
+
transform: translateX(0);
|
|
676
|
+
}
|
|
677
|
+
.main-content {
|
|
678
|
+
margin-left: 0;
|
|
679
|
+
}
|
|
680
|
+
.content {
|
|
681
|
+
padding: 1.5rem 1rem;
|
|
682
|
+
}
|
|
683
|
+
.top-bar {
|
|
684
|
+
padding: 0.7rem 1rem;
|
|
685
|
+
}
|
|
686
|
+
h2 {
|
|
687
|
+
font-size: 1.5rem;
|
|
688
|
+
}
|
|
689
|
+
.card-grid {
|
|
690
|
+
grid-template-columns: 1fr 1fr;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
@media (max-width: 500px) {
|
|
695
|
+
.card-grid {
|
|
696
|
+
grid-template-columns: 1fr;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
/* Scrollbar */
|
|
701
|
+
::-webkit-scrollbar {
|
|
702
|
+
width: 6px;
|
|
703
|
+
height: 6px;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
::-webkit-scrollbar-track {
|
|
707
|
+
background: var(--bg);
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
::-webkit-scrollbar-thumb {
|
|
711
|
+
background: var(--border);
|
|
712
|
+
border-radius: 10px;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
::-webkit-scrollbar-thumb:hover {
|
|
716
|
+
background: var(--text-muted);
|
|
717
|
+
}
|
|
718
|
+
</style>
|
|
719
|
+
</head>
|
|
720
|
+
<body>
|
|
721
|
+
|
|
722
|
+
<!-- ===== SIDEBAR OVERLAY ===== -->
|
|
723
|
+
<div class="sidebar-overlay" id="sidebarOverlay"></div>
|
|
724
|
+
|
|
725
|
+
<!-- ===== SIDEBAR ===== -->
|
|
726
|
+
<aside class="sidebar" id="sidebar">
|
|
727
|
+
<div class="sidebar-logo">
|
|
728
|
+
<img src="/imgs/logo-square.png" alt="logo" onerror="this.style.display='none';this.parentElement.querySelector('span').innerHTML='<i class=\'fas fa-cube\'></i> tinny<strong>backend</strong>'">
|
|
729
|
+
<span>tinny<strong>backend</strong></span>
|
|
730
|
+
</div>
|
|
731
|
+
<div class="sidebar-title">documentation</div>
|
|
732
|
+
|
|
733
|
+
<button class="nav-item active" data-section="overview">
|
|
734
|
+
<i class="fas fa-compass"></i> Overview
|
|
735
|
+
</button>
|
|
736
|
+
<button class="nav-item" data-section="getting-started">
|
|
737
|
+
<i class="fas fa-rocket"></i> Getting Started
|
|
738
|
+
</button>
|
|
739
|
+
<button class="nav-item" data-section="server-class">
|
|
740
|
+
<i class="fas fa-server"></i> Server Class
|
|
741
|
+
</button>
|
|
742
|
+
<button class="nav-item" data-section="routing">
|
|
743
|
+
<i class="fas fa-route"></i> Routing
|
|
744
|
+
</button>
|
|
745
|
+
<button class="nav-item" data-section="middleware">
|
|
746
|
+
<i class="fas fa-shield-alt"></i> Middleware
|
|
747
|
+
</button>
|
|
748
|
+
<button class="nav-item" data-section="static-files">
|
|
749
|
+
<i class="fas fa-folder-open"></i> Static Files
|
|
750
|
+
</button>
|
|
751
|
+
<button class="nav-item" data-section="cookies">
|
|
752
|
+
<i class="fas fa-cookie-bite"></i> Cookies
|
|
753
|
+
</button>
|
|
754
|
+
<button class="nav-item" data-section="admin-monitor">
|
|
755
|
+
<i class="fas fa-chart-line"></i> Admin Monitor
|
|
756
|
+
</button>
|
|
757
|
+
<button class="nav-item" data-section="types">
|
|
758
|
+
<i class="fas fa-code"></i> Types Reference
|
|
759
|
+
</button>
|
|
760
|
+
|
|
761
|
+
<div class="sidebar-footer">
|
|
762
|
+
<a href="https://github.com/iaceene" target="_blank" rel="noopener" class="github-btn">
|
|
763
|
+
<i class="fab fa-github"></i> GitHub Repository
|
|
764
|
+
</a>
|
|
765
|
+
</div>
|
|
766
|
+
</aside>
|
|
767
|
+
|
|
768
|
+
<!-- ===== MAIN CONTENT ===== -->
|
|
769
|
+
<div class="main-content" id="mainContent">
|
|
770
|
+
<!-- Top bar -->
|
|
771
|
+
<div class="top-bar">
|
|
772
|
+
<div class="top-bar-left">
|
|
773
|
+
<button class="sidebar-toggle" id="sidebarToggle" title="Toggle sidebar">
|
|
774
|
+
<i class="fas fa-bars"></i>
|
|
775
|
+
</button>
|
|
776
|
+
<div class="version-badge">
|
|
777
|
+
<i class="fas fa-code-branch"></i> v2.0 · full documentation
|
|
778
|
+
</div>
|
|
779
|
+
</div>
|
|
780
|
+
<button class="theme-toggle" id="themeToggle" title="Toggle dark/light mode">
|
|
781
|
+
<i class="fas fa-moon"></i>
|
|
782
|
+
</button>
|
|
783
|
+
</div>
|
|
784
|
+
|
|
785
|
+
<!-- Content sections -->
|
|
786
|
+
<div class="content" id="contentArea">
|
|
787
|
+
|
|
788
|
+
<!-- ========== OVERVIEW ========== -->
|
|
789
|
+
<div id="overview" class="section">
|
|
790
|
+
<h2><i class="fas fa-cube"></i> TinnyBackend Framework</h2>
|
|
791
|
+
<div class="subtitle">A lightweight, type-safe HTTP server with routing, middleware, cookies, static files, and built-in admin monitoring.</div>
|
|
792
|
+
|
|
793
|
+
<div class="card-grid">
|
|
794
|
+
<div class="stat-card">
|
|
795
|
+
<i class="fas fa-bolt"></i>
|
|
796
|
+
<strong>HTTP Methods</strong>
|
|
797
|
+
<p>GET · POST · PUT · DELETE</p>
|
|
798
|
+
</div>
|
|
799
|
+
<div class="stat-card">
|
|
800
|
+
<i class="fas fa-cookie-bite"></i>
|
|
801
|
+
<strong>Cookie Engine</strong>
|
|
802
|
+
<p>Native get/set/add with parsing</p>
|
|
803
|
+
</div>
|
|
804
|
+
<div class="stat-card">
|
|
805
|
+
<i class="fas fa-shield"></i>
|
|
806
|
+
<strong>Middleware Chain</strong>
|
|
807
|
+
<p>Pre-handler execution with next()</p>
|
|
808
|
+
</div>
|
|
809
|
+
<div class="stat-card">
|
|
810
|
+
<i class="fas fa-folder-tree"></i>
|
|
811
|
+
<strong>Static Directory</strong>
|
|
812
|
+
<p>Auto-index with MIME detection</p>
|
|
813
|
+
</div>
|
|
814
|
+
<div class="stat-card">
|
|
815
|
+
<i class="fas fa-chart-line"></i>
|
|
816
|
+
<strong>Admin Monitor</strong>
|
|
817
|
+
<p>Built-in dashboard with JWT auth</p>
|
|
818
|
+
</div>
|
|
819
|
+
<div class="stat-card">
|
|
820
|
+
<i class="fas fa-paint-brush"></i>
|
|
821
|
+
<strong>Decorators</strong>
|
|
822
|
+
<p>Attach custom metadata to server</p>
|
|
823
|
+
</div>
|
|
824
|
+
</div>
|
|
825
|
+
|
|
826
|
+
<h3>Key Features</h3>
|
|
827
|
+
<ul>
|
|
828
|
+
<li><strong>TypeScript-first</strong> with full type definitions</li>
|
|
829
|
+
<li><strong>Path parameters</strong> with automatic parsing (<code>/users/:id</code>)</li>
|
|
830
|
+
<li><strong>Middleware support</strong> with chain execution</li>
|
|
831
|
+
<li><strong>Cookie management</strong> with get/set/add operations</li>
|
|
832
|
+
<li><strong>Static file serving</strong> with MIME type detection</li>
|
|
833
|
+
<li><strong>Admin monitor</strong> with JWT authentication</li>
|
|
834
|
+
<li><strong>Decorators</strong> for sharing data across routes</li>
|
|
835
|
+
<li><strong>Built-in logging</strong> with colored console output</li>
|
|
836
|
+
<li><strong>Session management</strong> with IP-based tracking</li>
|
|
837
|
+
</ul>
|
|
838
|
+
|
|
839
|
+
<div class="note">
|
|
840
|
+
<strong><i class="fas fa-lightbulb"></i> Quick Start:</strong> Create a server instance, add routes, and start listening in just a few lines of code.
|
|
841
|
+
</div>
|
|
842
|
+
</div>
|
|
843
|
+
|
|
844
|
+
<!-- ========== GETTING STARTED ========== -->
|
|
845
|
+
<div id="getting-started" class="section hidden-section">
|
|
846
|
+
<h2><i class="fas fa-rocket"></i> Getting Started</h2>
|
|
847
|
+
<div class="subtitle">Install and set up your first TinnyBackend server.</div>
|
|
848
|
+
|
|
849
|
+
<h3>Installation</h3>
|
|
850
|
+
<div class="code-block">
|
|
851
|
+
<span class="code-label">bash</span>
|
|
852
|
+
<pre><code class="language-bash">npm install tinny-backend</code></pre>
|
|
853
|
+
</div>
|
|
854
|
+
|
|
855
|
+
<h3>Basic Example</h3>
|
|
856
|
+
<div class="code-block">
|
|
857
|
+
<span class="code-label">typescript</span>
|
|
858
|
+
<pre><code class="language-typescript">import Server from 'tinny-backend';
|
|
859
|
+
|
|
860
|
+
const app = new Server({ port: 3000, hostname: 'localhost' });
|
|
861
|
+
|
|
862
|
+
app.add({
|
|
863
|
+
method: 'GET',
|
|
864
|
+
path: '/hello',
|
|
865
|
+
handler: (req, res) => {
|
|
866
|
+
res.send(200, { message: 'Hello World!' });
|
|
867
|
+
}
|
|
868
|
+
});
|
|
869
|
+
|
|
870
|
+
app.listen(true, () => {
|
|
871
|
+
console.log('Server running at http://localhost:3000');
|
|
872
|
+
});</code></pre>
|
|
873
|
+
</div>
|
|
874
|
+
|
|
875
|
+
<h3>Environment Variables</h3>
|
|
876
|
+
<p>Create a <code>.env</code> file for admin monitor authentication:</p>
|
|
877
|
+
<div class="code-block">
|
|
878
|
+
<span class="code-label">env</span>
|
|
879
|
+
<pre><code class="language-bash">ADMIN_USERNAME=admin
|
|
880
|
+
ADMIN_PASSWORD=secure_password_here
|
|
881
|
+
ADMIN_KEY=your_secret_jwt_key</code></pre>
|
|
882
|
+
</div>
|
|
883
|
+
|
|
884
|
+
<div class="note warning">
|
|
885
|
+
<strong><i class="fas fa-exclamation-triangle"></i> Important:</strong> Never commit your <code>.env</code> file to version control. Always use strong passwords and JWT secrets.
|
|
886
|
+
</div>
|
|
887
|
+
</div>
|
|
888
|
+
|
|
889
|
+
<!-- ========== SERVER CLASS ========== -->
|
|
890
|
+
<div id="server-class" class="section hidden-section">
|
|
891
|
+
<h2><i class="fas fa-server"></i> Server Class</h2>
|
|
892
|
+
<div class="subtitle">The core class that creates and manages the HTTP server instance.</div>
|
|
893
|
+
|
|
894
|
+
<h3>Constructor</h3>
|
|
895
|
+
<details open>
|
|
896
|
+
<summary><i class="fas fa-cog"></i> new Server(options: ServerOptions)</summary>
|
|
897
|
+
<table class="info-table">
|
|
898
|
+
<thead>
|
|
899
|
+
<tr><th>Option</th><th>Type</th><th>Default</th><th>Description</th></tr>
|
|
900
|
+
</thead>
|
|
901
|
+
<tbody>
|
|
902
|
+
<tr><td>port</td><td>number</td><td>3000</td><td>Port number to listen on</td></tr>
|
|
903
|
+
<tr><td>hostname</td><td>string</td><td>"localhost"</td><td>Hostname for the server</td></tr>
|
|
904
|
+
<tr><td>ServerName</td><td>string</td><td>"Server"</td><td>Name used in logs</td></tr>
|
|
905
|
+
<tr><td>DefaultHandler</td><td>HandlerFun</td><td>404 handler</td><td>Fallback handler for unmatched routes</td></tr>
|
|
906
|
+
</tbody>
|
|
907
|
+
</table>
|
|
908
|
+
</details>
|
|
909
|
+
|
|
910
|
+
<h3>Core Methods</h3>
|
|
911
|
+
|
|
912
|
+
<details>
|
|
913
|
+
<summary><i class="fas fa-headphones"></i> .listen(enableMon?, callback?)</summary>
|
|
914
|
+
<p>Starts the HTTP server on the configured port.</p>
|
|
915
|
+
<ul>
|
|
916
|
+
<li><strong>enableMon</strong> (boolean): If <code>true</code>, launches admin monitor on <code>port + 1</code></li>
|
|
917
|
+
<li><strong>callback</strong> (function): Called when server starts listening</li>
|
|
918
|
+
</ul>
|
|
919
|
+
<div class="code-block">
|
|
920
|
+
<span class="code-label">example</span>
|
|
921
|
+
<pre><code class="language-typescript">app.listen(true, () => {
|
|
922
|
+
console.log('Server + monitor running');
|
|
923
|
+
});</code></pre>
|
|
924
|
+
</div>
|
|
925
|
+
</details>
|
|
926
|
+
|
|
927
|
+
<details>
|
|
928
|
+
<summary><i class="fas fa-paint-brush"></i> .decorate(name, data) & .useDecorator(name)</summary>
|
|
929
|
+
<p>Store and retrieve custom data on the server instance. Useful for sharing database connections, configs, or service instances across handlers.</p>
|
|
930
|
+
<div class="code-block">
|
|
931
|
+
<span class="code-label">example</span>
|
|
932
|
+
<pre><code class="language-typescript">server.decorate('db', databaseConnection);
|
|
933
|
+
server.decorate('config', { version: '2.0' });
|
|
934
|
+
|
|
935
|
+
// Later in a handler:
|
|
936
|
+
const db = server.useDecorator('db');
|
|
937
|
+
const config = server.useDecorator('config');</code></pre>
|
|
938
|
+
</div>
|
|
939
|
+
</details>
|
|
940
|
+
|
|
941
|
+
<details>
|
|
942
|
+
<summary><i class="fas fa-network-wired"></i> .getHost() & .getPort()</summary>
|
|
943
|
+
<p>Returns the configured hostname and port number respectively.</p>
|
|
944
|
+
<div class="code-block">
|
|
945
|
+
<span class="code-label">example</span>
|
|
946
|
+
<pre><code class="language-typescript">console.log(server.getHost()); // "localhost"
|
|
947
|
+
console.log(server.getPort()); // 3000</code></pre>
|
|
948
|
+
</div>
|
|
949
|
+
</details>
|
|
950
|
+
|
|
951
|
+
<details>
|
|
952
|
+
<summary><i class="fas fa-clipboard-list"></i> .log(message, type?)</summary>
|
|
953
|
+
<p>Log messages with timestamp and color coding. Messages are also stored in the internal logs array.</p>
|
|
954
|
+
<div class="code-block">
|
|
955
|
+
<span class="code-label">example</span>
|
|
956
|
+
<pre><code class="language-typescript">server.log('User authenticated', 'message');
|
|
957
|
+
server.log('Database connection failed', 'error');</code></pre>
|
|
958
|
+
</div>
|
|
959
|
+
</details>
|
|
960
|
+
</div>
|
|
961
|
+
|
|
962
|
+
<!-- ========== ROUTING ========== -->
|
|
963
|
+
<div id="routing" class="section hidden-section">
|
|
964
|
+
<h2><i class="fas fa-route"></i> Routing</h2>
|
|
965
|
+
<div class="subtitle">Register routes with method, path, handlers, and middleware.</div>
|
|
966
|
+
|
|
967
|
+
<h3>Adding Routes</h3>
|
|
968
|
+
<details open>
|
|
969
|
+
<summary><i class="fas fa-plus-circle"></i> .add(option: AddOption)</summary>
|
|
970
|
+
<table class="info-table">
|
|
971
|
+
<thead>
|
|
972
|
+
<tr><th>Property</th><th>Type</th><th>Required</th><th>Description</th></tr>
|
|
973
|
+
</thead>
|
|
974
|
+
<tbody>
|
|
975
|
+
<tr><td>method</td><td>Methods</td><td>Yes</td><td>"GET" | "POST" | "PUT" | "DELETE"</td></tr>
|
|
976
|
+
<tr><td>path</td><td>string</td><td>Yes</td><td>URL path (supports :params)</td></tr>
|
|
977
|
+
<tr><td>handler</td><td>HandlerFun</td><td>Yes</td><td>Main request handler function</td></tr>
|
|
978
|
+
<tr><td>middelWares</td><td>HandlerFun[]</td><td>No</td><td>Array of middleware functions</td></tr>
|
|
979
|
+
<tr><td>next</td><td>HandlerFun</td><td>No</td><td>Callback executed after main handler</td></tr>
|
|
980
|
+
</tbody>
|
|
981
|
+
</table>
|
|
982
|
+
|
|
983
|
+
<div class="code-block">
|
|
984
|
+
<span class="code-label">example</span>
|
|
985
|
+
<pre><code class="language-typescript">server.add({
|
|
986
|
+
method: 'POST',
|
|
987
|
+
path: '/api/users/:id',
|
|
988
|
+
handler: (req, res) => {
|
|
989
|
+
const userId = req.params.id;
|
|
990
|
+
res.send(200, { user: userId, data: req.body });
|
|
991
|
+
},
|
|
992
|
+
middelWares: [authMiddleware, loggerMiddleware],
|
|
993
|
+
next: (req, res) => {
|
|
994
|
+
console.log('Response sent for:', req.url);
|
|
995
|
+
}
|
|
996
|
+
});</code></pre>
|
|
997
|
+
</div>
|
|
998
|
+
</details>
|
|
999
|
+
|
|
1000
|
+
<h3>Path Parameters</h3>
|
|
1001
|
+
<p>Define dynamic routes using <code>:param</code> syntax. Parameters are automatically parsed and available in <code>req.params</code>.</p>
|
|
1002
|
+
<div class="code-block">
|
|
1003
|
+
<span class="code-label">example</span>
|
|
1004
|
+
<pre><code class="language-typescript">app.add({
|
|
1005
|
+
method: 'GET',
|
|
1006
|
+
path: '/users/:userId/posts/:postId',
|
|
1007
|
+
handler: (req, res) => {
|
|
1008
|
+
const { userId, postId } = req.params;
|
|
1009
|
+
res.send(200, { userId, postId });
|
|
1010
|
+
}
|
|
1011
|
+
});</code></pre>
|
|
1012
|
+
</div>
|
|
1013
|
+
|
|
1014
|
+
<h3>Request Object (ServerReq)</h3>
|
|
1015
|
+
<p>Extends Node.js <code>http.IncomingMessage</code> with additional properties:</p>
|
|
1016
|
+
<table class="info-table">
|
|
1017
|
+
<thead>
|
|
1018
|
+
<tr><th>Property</th><th>Type</th><th>Description</th></tr>
|
|
1019
|
+
</thead>
|
|
1020
|
+
<tbody>
|
|
1021
|
+
<tr><td>body</td><td>any</td><td>Parsed JSON body (auto-parsed from request stream)</td></tr>
|
|
1022
|
+
<tr><td>params</td><td>Record<string, string></td><td>URL path parameters</td></tr>
|
|
1023
|
+
<tr><td>queries</td><td>object</td><td>Parsed query string parameters</td></tr>
|
|
1024
|
+
<tr><td>ip</td><td>string</td><td>Client IP address</td></tr>
|
|
1025
|
+
<tr><td>server</td><td>Server</td><td>Reference to the Server instance</td></tr>
|
|
1026
|
+
</tbody>
|
|
1027
|
+
</table>
|
|
1028
|
+
|
|
1029
|
+
<h3>Response Object (ServerRes)</h3>
|
|
1030
|
+
<p>Extends Node.js <code>http.ServerResponse</code> with helper methods:</p>
|
|
1031
|
+
<table class="info-table">
|
|
1032
|
+
<thead>
|
|
1033
|
+
<tr><th>Method</th><th>Description</th></tr>
|
|
1034
|
+
</thead>
|
|
1035
|
+
<tbody>
|
|
1036
|
+
<tr><td>.send(status, data?, headers?)</td><td>Send JSON response. Auto-sets Content-Type</td></tr>
|
|
1037
|
+
<tr><td>.sendFile(status, contentType, data, headers?)</td><td>Stream a file from disk with MIME type</td></tr>
|
|
1038
|
+
<tr><td>.isClosed</td><td>Boolean flag preventing duplicate responses</td></tr>
|
|
1039
|
+
</tbody>
|
|
1040
|
+
</table>
|
|
1041
|
+
</div>
|
|
1042
|
+
|
|
1043
|
+
<!-- ========== MIDDLEWARE ========== -->
|
|
1044
|
+
<div id="middleware" class="section hidden-section">
|
|
1045
|
+
<h2><i class="fas fa-shield-alt"></i> Middleware</h2>
|
|
1046
|
+
<div class="subtitle">Execute functions before the main handler for authentication, logging, validation, etc.</div>
|
|
1047
|
+
|
|
1048
|
+
<h3>How Middleware Works</h3>
|
|
1049
|
+
<p>Middleware functions are executed <strong>in order</strong> before the main handler. Each middleware receives <code>(req, res)</code>. If a middleware sends a response (e.g., 401), the <code>isClosed</code> flag prevents the main handler from executing.</p>
|
|
1050
|
+
|
|
1051
|
+
<details open>
|
|
1052
|
+
<summary><i class="fas fa-lock"></i> Authentication Middleware</summary>
|
|
1053
|
+
<div class="code-block">
|
|
1054
|
+
<span class="code-label">example</span>
|
|
1055
|
+
<pre><code class="language-typescript">const requireAuth = (req, res) => {
|
|
1056
|
+
const token = res.getCookie('auth_token');
|
|
1057
|
+
if (!token || token.value !== 'valid-secret') {
|
|
1058
|
+
res.send(401, { error: 'Unauthorized access' });
|
|
1059
|
+
return; // isClosed = true, main handler won't run
|
|
1060
|
+
}
|
|
1061
|
+
};
|
|
1062
|
+
|
|
1063
|
+
app.add({
|
|
1064
|
+
method: 'GET',
|
|
1065
|
+
path: '/admin/dashboard',
|
|
1066
|
+
handler: adminDashboardHandler,
|
|
1067
|
+
middelWares: [requireAuth]
|
|
1068
|
+
});</code></pre>
|
|
1069
|
+
</div>
|
|
1070
|
+
</details>
|
|
1071
|
+
|
|
1072
|
+
<details>
|
|
1073
|
+
<summary><i class="fas fa-clock"></i> Request Logger Middleware</summary>
|
|
1074
|
+
<div class="code-block">
|
|
1075
|
+
<span class="code-label">example</span>
|
|
1076
|
+
<pre><code class="language-typescript">const requestLogger = (req, res) => {
|
|
1077
|
+
const start = Date.now();
|
|
1078
|
+
const originalSend = res.send;
|
|
1079
|
+
|
|
1080
|
+
res.send = function(...args) {
|
|
1081
|
+
const duration = Date.now() - start;
|
|
1082
|
+
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url} - ${duration}ms`);
|
|
1083
|
+
return originalSend.apply(this, args);
|
|
1084
|
+
};
|
|
1085
|
+
};</code></pre>
|
|
1086
|
+
</div>
|
|
1087
|
+
</details>
|
|
1088
|
+
|
|
1089
|
+
<h3>The <code>next</code> Callback</h3>
|
|
1090
|
+
<p>After the main handler executes (and if the response isn't closed), the optional <code>next</code> function runs. This is useful for cleanup, logging, or post-processing:</p>
|
|
1091
|
+
<div class="code-block">
|
|
1092
|
+
<span class="code-label">example</span>
|
|
1093
|
+
<pre><code class="language-typescript">app.add({
|
|
1094
|
+
method: 'DELETE',
|
|
1095
|
+
path: '/cache',
|
|
1096
|
+
handler: clearCacheHandler,
|
|
1097
|
+
next: (req, res) => {
|
|
1098
|
+
console.log('Cache cleared successfully');
|
|
1099
|
+
}
|
|
1100
|
+
});</code></pre>
|
|
1101
|
+
</div>
|
|
1102
|
+
</div>
|
|
1103
|
+
|
|
1104
|
+
<!-- ========== STATIC FILES ========== -->
|
|
1105
|
+
<div id="static-files" class="section hidden-section">
|
|
1106
|
+
<h2><i class="fas fa-folder-open"></i> Static Files</h2>
|
|
1107
|
+
<div class="subtitle">Serve entire directories automatically with MIME type detection.</div>
|
|
1108
|
+
|
|
1109
|
+
<h3>.servDir() Method</h3>
|
|
1110
|
+
<details open>
|
|
1111
|
+
<summary><i class="fas fa-folder"></i> .servDir(directoryPath, urlPrefix, middleware?)</summary>
|
|
1112
|
+
<p>Recursively reads a directory and creates GET routes for every file. Files named <code>index.*</code> are also mapped to the directory path as default files.</p>
|
|
1113
|
+
|
|
1114
|
+
<table class="info-table">
|
|
1115
|
+
<thead>
|
|
1116
|
+
<tr><th>Parameter</th><th>Description</th></tr>
|
|
1117
|
+
</thead>
|
|
1118
|
+
<tbody>
|
|
1119
|
+
<tr><td>directoryPath</td><td>Relative path to the directory (e.g., "./public")</td></tr>
|
|
1120
|
+
<tr><td>urlPrefix</td><td>URL prefix for the routes (e.g., "assets")</td></tr>
|
|
1121
|
+
<tr><td>middleware</td><td>Optional middleware array for all routes</td></tr>
|
|
1122
|
+
</tbody>
|
|
1123
|
+
</table>
|
|
1124
|
+
|
|
1125
|
+
<div class="code-block">
|
|
1126
|
+
<span class="code-label">example</span>
|
|
1127
|
+
<pre><code class="language-typescript">// Serve ./public directory at /assets/*
|
|
1128
|
+
await server.servDir('./public', 'assets');
|
|
1129
|
+
|
|
1130
|
+
// With authentication middleware
|
|
1131
|
+
await server.servDir('./private', 'admin', [requireAuth]);</code></pre>
|
|
1132
|
+
</div>
|
|
1133
|
+
</details>
|
|
1134
|
+
|
|
1135
|
+
<h3>Supported MIME Types</h3>
|
|
1136
|
+
<table class="info-table">
|
|
1137
|
+
<thead>
|
|
1138
|
+
<tr><th>Extension</th><th>MIME Type</th></tr>
|
|
1139
|
+
</thead>
|
|
1140
|
+
<tbody>
|
|
1141
|
+
<tr><td>.html</td><td>text/html</td></tr>
|
|
1142
|
+
<tr><td>.css</td><td>text/css</td></tr>
|
|
1143
|
+
<tr><td>.js</td><td>text/javascript</td></tr>
|
|
1144
|
+
<tr><td>.json</td><td>application/json</td></tr>
|
|
1145
|
+
<tr><td>.png</td><td>image/png</td></tr>
|
|
1146
|
+
<tr><td>.jpg / .jpeg</td><td>image/jpeg</td></tr>
|
|
1147
|
+
<tr><td>.mp4</td><td>video/mp4</td></tr>
|
|
1148
|
+
<tr><td>other</td><td>application/octet-stream</td></tr>
|
|
1149
|
+
</tbody>
|
|
1150
|
+
</table>
|
|
1151
|
+
|
|
1152
|
+
<h3>.SendFile() Method</h3>
|
|
1153
|
+
<details>
|
|
1154
|
+
<summary><i class="fas fa-file"></i> .SendFile(res, filePath, status)</summary>
|
|
1155
|
+
<p>Internal method used by <code>servDir()</code>. Can also be called directly to serve individual files. Automatically falls back to 404.html if file not found.</p>
|
|
1156
|
+
<div class="code-block">
|
|
1157
|
+
<span class="code-label">example</span>
|
|
1158
|
+
<pre><code class="language-typescript">app.add({
|
|
1159
|
+
method: 'GET',
|
|
1160
|
+
path: '/download/report',
|
|
1161
|
+
handler: async (req, res) => {
|
|
1162
|
+
await server.SendFile(res, 'reports/summary.pdf', 200);
|
|
1163
|
+
}
|
|
1164
|
+
});</code></pre>
|
|
1165
|
+
</div>
|
|
1166
|
+
</details>
|
|
1167
|
+
</div>
|
|
1168
|
+
|
|
1169
|
+
<!-- ========== COOKIES ========== -->
|
|
1170
|
+
<div id="cookies" class="section hidden-section">
|
|
1171
|
+
<h2><i class="fas fa-cookie-bite"></i> Cookies</h2>
|
|
1172
|
+
<div class="subtitle">Complete cookie management with get, set, and add operations.</div>
|
|
1173
|
+
|
|
1174
|
+
<h3>Cookie Methods</h3>
|
|
1175
|
+
<table class="info-table">
|
|
1176
|
+
<thead>
|
|
1177
|
+
<tr><th>Method</th><th>Description</th></tr>
|
|
1178
|
+
</thead>
|
|
1179
|
+
<tbody>
|
|
1180
|
+
<tr><td><code>res.getCookie(name)</code></td><td>Get cookie by name, returns <code>Cookie | null</code></td></tr>
|
|
1181
|
+
<tr><td><code>res.setCookie(name, value)</code></td><td>Update existing cookie value</td></tr>
|
|
1182
|
+
<tr><td><code>res.addCookie(name, value)</code></td><td>Add new cookie (skips if exists)</td></tr>
|
|
1183
|
+
<tr><td><code>res.getAllCookies()</code></td><td>Returns all cookies as <code>string[]</code></td></tr>
|
|
1184
|
+
</tbody>
|
|
1185
|
+
</table>
|
|
1186
|
+
|
|
1187
|
+
<div class="code-block">
|
|
1188
|
+
<span class="code-label">example</span>
|
|
1189
|
+
<pre><code class="language-typescript">app.add({
|
|
1190
|
+
method: 'POST',
|
|
1191
|
+
path: '/login',
|
|
1192
|
+
handler: (req, res) => {
|
|
1193
|
+
// Set a cookie
|
|
1194
|
+
res.addCookie('session_id', 'abc123');
|
|
1195
|
+
|
|
1196
|
+
// Get a cookie
|
|
1197
|
+
const session = res.getCookie('session_id');
|
|
1198
|
+
|
|
1199
|
+
// Update a cookie
|
|
1200
|
+
res.setCookie('session_id', 'xyz789');
|
|
1201
|
+
|
|
1202
|
+
// Get all cookies
|
|
1203
|
+
const allCookies = res.getAllCookies();
|
|
1204
|
+
|
|
1205
|
+
res.send(200, {
|
|
1206
|
+
message: 'Cookie set',
|
|
1207
|
+
cookies: allCookies
|
|
1208
|
+
});
|
|
1209
|
+
}
|
|
1210
|
+
});</code></pre>
|
|
1211
|
+
</div>
|
|
1212
|
+
|
|
1213
|
+
<div class="note">
|
|
1214
|
+
<strong><i class="fas fa-info-circle"></i> Note:</strong> Cookies are automatically parsed from the <code>Cookie</code> header and stored in memory for the duration of the request.
|
|
1215
|
+
</div>
|
|
1216
|
+
</div>
|
|
1217
|
+
|
|
1218
|
+
<!-- ========== ADMIN MONITOR ========== -->
|
|
1219
|
+
<div id="admin-monitor" class="section hidden-section">
|
|
1220
|
+
<h2><i class="fas fa-chart-line"></i> Admin Monitor</h2>
|
|
1221
|
+
<div class="subtitle">Built-in web dashboard for server administration with JWT authentication.</div>
|
|
1222
|
+
|
|
1223
|
+
<h3>Enabling the Monitor</h3>
|
|
1224
|
+
<details open>
|
|
1225
|
+
<summary><i class="fas fa-user-shield"></i> .listen(true, callback)</summary>
|
|
1226
|
+
<p>When <code>enableMon</code> is <code>true</code>, a second server instance starts on <code>port + 1</code> with the following features:</p>
|
|
1227
|
+
|
|
1228
|
+
<h4>Authentication Flow</h4>
|
|
1229
|
+
<ol>
|
|
1230
|
+
<li>User visits <code>/</code> → sees login form</li>
|
|
1231
|
+
<li>Submits credentials to <code>POST /api/login</code></li>
|
|
1232
|
+
<li>On success, server sets JWT cookie <code>token</code></li>
|
|
1233
|
+
<li>Subsequent requests check this cookie via <code>isAuthed()</code></li>
|
|
1234
|
+
<li>Protected routes use <code>Auth</code> middleware</li>
|
|
1235
|
+
</ol>
|
|
1236
|
+
|
|
1237
|
+
<h4>Monitor Routes</h4>
|
|
1238
|
+
<table class="info-table">
|
|
1239
|
+
<thead>
|
|
1240
|
+
<tr><th>Route</th><th>Method</th><th>Auth</th><th>Description</th></tr>
|
|
1241
|
+
</thead>
|
|
1242
|
+
<tbody>
|
|
1243
|
+
<tr><td>/</td><td>GET</td><td>No</td><td>Login page or admin dashboard (if authenticated)</td></tr>
|
|
1244
|
+
<tr><td>/api/login</td><td>POST</td><td>No</td><td>Authenticate with username/password</td></tr>
|
|
1245
|
+
<tr><td>/api/logout</td><td>GET</td><td>Yes</td><td>Logout and invalidate session</td></tr>
|
|
1246
|
+
<tr><td>/admin/status</td><td>GET</td><td>Yes</td><td>Server status endpoint (CPU, memory, sessions, etc.)</td></tr>
|
|
1247
|
+
<tr><td>/messages</td><td>GET</td><td>Yes</td><td>Server logs endpoint</td></tr>
|
|
1248
|
+
<tr><td>/docs</td><td>GET</td><td>No</td><td>Serves documentation</td></tr>
|
|
1249
|
+
<tr><td>/imgs</td><td>GET</td><td>No</td><td>Serves images</td></tr>
|
|
1250
|
+
</tbody>
|
|
1251
|
+
</table>
|
|
1252
|
+
</details>
|
|
1253
|
+
|
|
1254
|
+
<div class="code-block">
|
|
1255
|
+
<span class="code-label">example</span>
|
|
1256
|
+
<pre><code class="language-typescript">// Start server with monitor
|
|
1257
|
+
app.listen(true, () => {
|
|
1258
|
+
console.log('Main server: http://localhost:3000');
|
|
1259
|
+
console.log('Monitor: http://localhost:3001');
|
|
1260
|
+
});</code></pre>
|
|
1261
|
+
</div>
|
|
1262
|
+
|
|
1263
|
+
<h3>Environment Configuration</h3>
|
|
1264
|
+
<p>Create a <code>.env</code> file with:</p>
|
|
1265
|
+
<div class="code-block">
|
|
1266
|
+
<span class="code-label">env</span>
|
|
1267
|
+
<pre><code class="language-bash">ADMIN_USERNAME=admin
|
|
1268
|
+
ADMIN_PASSWORD=your_secure_password
|
|
1269
|
+
ADMIN_KEY=your_jwt_secret_key</code></pre>
|
|
1270
|
+
</div>
|
|
1271
|
+
|
|
1272
|
+
<div class="note warning">
|
|
1273
|
+
<strong><i class="fas fa-exclamation-triangle"></i> Security Note:</strong> Always use strong passwords and keep your JWT secret secure. The monitor uses JWT tokens with 1-hour expiration.
|
|
1274
|
+
</div>
|
|
1275
|
+
|
|
1276
|
+
<h3>Admin Sessions</h3>
|
|
1277
|
+
<p>The monitor tracks admin sessions with the following structure:</p>
|
|
1278
|
+
<div class="code-block">
|
|
1279
|
+
<span class="code-label">typescript</span>
|
|
1280
|
+
<pre><code class="language-typescript">type AdminSessions = {
|
|
1281
|
+
id: string; // Unique session ID
|
|
1282
|
+
ip: string; // Client IP address
|
|
1283
|
+
userAgent: string; // Browser user agent
|
|
1284
|
+
creation: number; // Timestamp of session creation
|
|
1285
|
+
isValid: boolean; // Session validity flag
|
|
1286
|
+
request: number; // Request counter
|
|
1287
|
+
}</code></pre>
|
|
1288
|
+
</div>
|
|
1289
|
+
</div>
|
|
1290
|
+
|
|
1291
|
+
<!-- ========== TYPES REFERENCE ========== -->
|
|
1292
|
+
<div id="types" class="section hidden-section">
|
|
1293
|
+
<h2><i class="fas fa-code"></i> Types Reference</h2>
|
|
1294
|
+
<div class="subtitle">Complete TypeScript type definitions.</div>
|
|
1295
|
+
|
|
1296
|
+
<details open>
|
|
1297
|
+
<summary><i class="fas fa-tag"></i> Methods</summary>
|
|
1298
|
+
<div class="code-block">
|
|
1299
|
+
<pre><code class="language-typescript">type Methods = "POST" | "GET" | "PUT" | "DELETE"</code></pre>
|
|
1300
|
+
</div>
|
|
1301
|
+
</details>
|
|
1302
|
+
|
|
1303
|
+
<details>
|
|
1304
|
+
<summary><i class="fas fa-tag"></i> HandlerFun</summary>
|
|
1305
|
+
<div class="code-block">
|
|
1306
|
+
<pre><code class="language-typescript">type HandlerFun = (req: ServerReq, res: ServerRes) => void | Promise<void></code></pre>
|
|
1307
|
+
</div>
|
|
1308
|
+
</details>
|
|
1309
|
+
|
|
1310
|
+
<details>
|
|
1311
|
+
<summary><i class="fas fa-tag"></i> AddOption</summary>
|
|
1312
|
+
<div class="code-block">
|
|
1313
|
+
<pre><code class="language-typescript">type AddOption = {
|
|
1314
|
+
method: Methods;
|
|
1315
|
+
path: string;
|
|
1316
|
+
handler: HandlerFun;
|
|
1317
|
+
middelWares?: HandlerFun[];
|
|
1318
|
+
next?: HandlerFun;
|
|
1319
|
+
}</code></pre>
|
|
1320
|
+
</div>
|
|
1321
|
+
</details>
|
|
1322
|
+
|
|
1323
|
+
<details>
|
|
1324
|
+
<summary><i class="fas fa-tag"></i> ServerOptions</summary>
|
|
1325
|
+
<div class="code-block">
|
|
1326
|
+
<pre><code class="language-typescript">type ServerOptions = {
|
|
1327
|
+
port?: number;
|
|
1328
|
+
hostname?: string;
|
|
1329
|
+
reqPerMinute?: number;
|
|
1330
|
+
DefaultHandler?: HandlerFun;
|
|
1331
|
+
ServerName?: string;
|
|
1332
|
+
}</code></pre>
|
|
1333
|
+
</div>
|
|
1334
|
+
</details>
|
|
1335
|
+
|
|
1336
|
+
<details>
|
|
1337
|
+
<summary><i class="fas fa-tag"></i> ServerReq</summary>
|
|
1338
|
+
<div class="code-block">
|
|
1339
|
+
<pre><code class="language-typescript">type ServerReq = http.IncomingMessage & {
|
|
1340
|
+
ReqUrl: URL | null;
|
|
1341
|
+
Query: URLSearchParams;
|
|
1342
|
+
queries: object;
|
|
1343
|
+
body?: any;
|
|
1344
|
+
ip: string;
|
|
1345
|
+
server: Server;
|
|
1346
|
+
params: Record<string, string>;
|
|
1347
|
+
}</code></pre>
|
|
1348
|
+
</div>
|
|
1349
|
+
</details>
|
|
1350
|
+
|
|
1351
|
+
<details>
|
|
1352
|
+
<summary><i class="fas fa-tag"></i> ServerRes</summary>
|
|
1353
|
+
<div class="code-block">
|
|
1354
|
+
<pre><code class="language-typescript">type ServerRes = http.ServerResponse & {
|
|
1355
|
+
body?: any;
|
|
1356
|
+
ip: string;
|
|
1357
|
+
send: (status: number, data?: any, headers?: object) => void;
|
|
1358
|
+
sendFile: (status: number, ContentType: string, data?: any, headers?: object) => void;
|
|
1359
|
+
isClosed: boolean;
|
|
1360
|
+
server: Server;
|
|
1361
|
+
getCookie: (name: string) => Cookie | null;
|
|
1362
|
+
setCookie: (name: string, value: string) => void;
|
|
1363
|
+
addCookie: (name: string, value: string) => void;
|
|
1364
|
+
getAllCookies: () => string[];
|
|
1365
|
+
}</code></pre>
|
|
1366
|
+
</div>
|
|
1367
|
+
</details>
|
|
1368
|
+
|
|
1369
|
+
<details>
|
|
1370
|
+
<summary><i class="fas fa-tag"></i> Cookie</summary>
|
|
1371
|
+
<div class="code-block">
|
|
1372
|
+
<pre><code class="language-typescript">type Cookie = { key: string; value: string }</code></pre>
|
|
1373
|
+
</div>
|
|
1374
|
+
</details>
|
|
1375
|
+
|
|
1376
|
+
<details>
|
|
1377
|
+
<summary><i class="fas fa-tag"></i> Session</summary>
|
|
1378
|
+
<div class="code-block">
|
|
1379
|
+
<pre><code class="language-typescript">type Session = {
|
|
1380
|
+
ip: string;
|
|
1381
|
+
lastReq: number;
|
|
1382
|
+
reqCount: number;
|
|
1383
|
+
key: string;
|
|
1384
|
+
banInterval: number;
|
|
1385
|
+
}</code></pre>
|
|
1386
|
+
</div>
|
|
1387
|
+
</details>
|
|
1388
|
+
|
|
1389
|
+
<details>
|
|
1390
|
+
<summary><i class="fas fa-tag"></i> AdminSessions</summary>
|
|
1391
|
+
<div class="code-block">
|
|
1392
|
+
<pre><code class="language-typescript">type AdminSessions = {
|
|
1393
|
+
id: string;
|
|
1394
|
+
ip: string;
|
|
1395
|
+
userAgent: string;
|
|
1396
|
+
creation: number;
|
|
1397
|
+
isValid: boolean;
|
|
1398
|
+
request: number;
|
|
1399
|
+
}</code></pre>
|
|
1400
|
+
</div>
|
|
1401
|
+
</details>
|
|
1402
|
+
|
|
1403
|
+
<details>
|
|
1404
|
+
<summary><i class="fas fa-tag"></i> Logs</summary>
|
|
1405
|
+
<div class="code-block">
|
|
1406
|
+
<pre><code class="language-typescript">type Logs = {
|
|
1407
|
+
message: string;
|
|
1408
|
+
type: "message" | "error";
|
|
1409
|
+
date: string;
|
|
1410
|
+
}</code></pre>
|
|
1411
|
+
</div>
|
|
1412
|
+
</details>
|
|
1413
|
+
|
|
1414
|
+
<h3>Complete Example</h3>
|
|
1415
|
+
<div class="code-block">
|
|
1416
|
+
<span class="code-label">typescript</span>
|
|
1417
|
+
<pre><code class="language-typescript">import Server from 'tinny-backend';
|
|
1418
|
+
|
|
1419
|
+
const app = new Server({
|
|
1420
|
+
port: 3000,
|
|
1421
|
+
hostname: '0.0.0.0',
|
|
1422
|
+
ServerName: 'MyAPI'
|
|
1423
|
+
});
|
|
1424
|
+
|
|
1425
|
+
// Decorators
|
|
1426
|
+
app.decorate('startTime', Date.now());
|
|
1427
|
+
app.decorate('config', { version: '1.0.0' });
|
|
1428
|
+
|
|
1429
|
+
// Middleware
|
|
1430
|
+
const auth = (req, res) => {
|
|
1431
|
+
const session = res.getCookie('session_id');
|
|
1432
|
+
if (!session) {
|
|
1433
|
+
res.send(401, { error: 'Unauthorized' });
|
|
1434
|
+
}
|
|
1435
|
+
};
|
|
1436
|
+
|
|
1437
|
+
// Routes
|
|
1438
|
+
app.add({
|
|
1439
|
+
method: 'GET',
|
|
1440
|
+
path: '/',
|
|
1441
|
+
handler: (req, res) => {
|
|
1442
|
+
const startTime = app.useDecorator('startTime');
|
|
1443
|
+
res.send(200, {
|
|
1444
|
+
status: 'running',
|
|
1445
|
+
uptime: Date.now() - startTime.data
|
|
1446
|
+
});
|
|
1447
|
+
}
|
|
1448
|
+
});
|
|
1449
|
+
|
|
1450
|
+
app.add({
|
|
1451
|
+
method: 'GET',
|
|
1452
|
+
path: '/api/users/:id',
|
|
1453
|
+
handler: (req, res) => {
|
|
1454
|
+
const userId = req.params.id;
|
|
1455
|
+
res.send(200, { userId, query: req.queries });
|
|
1456
|
+
},
|
|
1457
|
+
middelWares: [auth]
|
|
1458
|
+
});
|
|
1459
|
+
|
|
1460
|
+
app.add({
|
|
1461
|
+
method: 'POST',
|
|
1462
|
+
path: '/api/data',
|
|
1463
|
+
handler: (req, res) => {
|
|
1464
|
+
res.addCookie('data_processed', 'true');
|
|
1465
|
+
res.send(201, {
|
|
1466
|
+
received: req.body,
|
|
1467
|
+
cookies: res.getAllCookies()
|
|
1468
|
+
});
|
|
1469
|
+
}
|
|
1470
|
+
});
|
|
1471
|
+
|
|
1472
|
+
// Static files
|
|
1473
|
+
await app.servDir('./public', 'static');
|
|
1474
|
+
|
|
1475
|
+
// Start server with monitor
|
|
1476
|
+
app.listen(true, () => {
|
|
1477
|
+
console.log('Server: http://localhost:3000');
|
|
1478
|
+
console.log('Monitor: http://localhost:3001');
|
|
1479
|
+
});</code></pre>
|
|
1480
|
+
</div>
|
|
1481
|
+
</div>
|
|
1482
|
+
|
|
1483
|
+
</div>
|
|
1484
|
+
</div>
|
|
1485
|
+
|
|
1486
|
+
<!-- Highlight.js -->
|
|
1487
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
|
1488
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/javascript.min.js"></script>
|
|
1489
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/typescript.min.js"></script>
|
|
1490
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/bash.min.js"></script>
|
|
1491
|
+
|
|
1492
|
+
<script>
|
|
1493
|
+
(function() {
|
|
1494
|
+
// ===== THEME TOGGLE =====
|
|
1495
|
+
const html = document.documentElement;
|
|
1496
|
+
const themeToggle = document.getElementById('themeToggle');
|
|
1497
|
+
const hljsLight = document.getElementById('hljs-light');
|
|
1498
|
+
const hljsDark = document.getElementById('hljs-dark');
|
|
1499
|
+
const savedTheme = localStorage.getItem('tinnybackend-theme');
|
|
1500
|
+
|
|
1501
|
+
if (savedTheme) {
|
|
1502
|
+
html.setAttribute('data-theme', savedTheme);
|
|
1503
|
+
} else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
|
1504
|
+
html.setAttribute('data-theme', 'dark');
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
function updateThemeUI(theme) {
|
|
1508
|
+
const icon = themeToggle.querySelector('i');
|
|
1509
|
+
if (theme === 'dark') {
|
|
1510
|
+
icon.classList.remove('fa-moon');
|
|
1511
|
+
icon.classList.add('fa-sun');
|
|
1512
|
+
hljsLight.disabled = true;
|
|
1513
|
+
hljsDark.disabled = false;
|
|
1514
|
+
} else {
|
|
1515
|
+
icon.classList.remove('fa-sun');
|
|
1516
|
+
icon.classList.add('fa-moon');
|
|
1517
|
+
hljsLight.disabled = false;
|
|
1518
|
+
hljsDark.disabled = true;
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
updateThemeUI(html.getAttribute('data-theme'));
|
|
1523
|
+
|
|
1524
|
+
themeToggle.addEventListener('click', () => {
|
|
1525
|
+
const currentTheme = html.getAttribute('data-theme');
|
|
1526
|
+
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
|
|
1527
|
+
html.setAttribute('data-theme', newTheme);
|
|
1528
|
+
localStorage.setItem('tinnybackend-theme', newTheme);
|
|
1529
|
+
updateThemeUI(newTheme);
|
|
1530
|
+
});
|
|
1531
|
+
|
|
1532
|
+
if (window.matchMedia) {
|
|
1533
|
+
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
|
|
1534
|
+
if (!localStorage.getItem('tinnybackend-theme')) {
|
|
1535
|
+
const newTheme = e.matches ? 'dark' : 'light';
|
|
1536
|
+
html.setAttribute('data-theme', newTheme);
|
|
1537
|
+
updateThemeUI(newTheme);
|
|
1538
|
+
}
|
|
1539
|
+
});
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
// ===== SIDEBAR TOGGLE =====
|
|
1543
|
+
const sidebar = document.getElementById('sidebar');
|
|
1544
|
+
const mainContent = document.getElementById('mainContent');
|
|
1545
|
+
const sidebarToggle = document.getElementById('sidebarToggle');
|
|
1546
|
+
const sidebarOverlay = document.getElementById('sidebarOverlay');
|
|
1547
|
+
const navItems = document.querySelectorAll('.nav-item');
|
|
1548
|
+
|
|
1549
|
+
let sidebarVisible = true;
|
|
1550
|
+
|
|
1551
|
+
function updateSidebarState() {
|
|
1552
|
+
if (window.innerWidth <= 850) {
|
|
1553
|
+
sidebar.classList.add('collapsed');
|
|
1554
|
+
sidebar.classList.remove('mobile-open');
|
|
1555
|
+
mainContent.classList.add('expanded');
|
|
1556
|
+
sidebarOverlay.classList.remove('show');
|
|
1557
|
+
sidebarVisible = false;
|
|
1558
|
+
} else {
|
|
1559
|
+
sidebar.classList.remove('mobile-open');
|
|
1560
|
+
sidebarOverlay.classList.remove('show');
|
|
1561
|
+
if (sidebarVisible) {
|
|
1562
|
+
sidebar.classList.remove('collapsed');
|
|
1563
|
+
mainContent.classList.remove('expanded');
|
|
1564
|
+
} else {
|
|
1565
|
+
sidebar.classList.add('collapsed');
|
|
1566
|
+
mainContent.classList.add('expanded');
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
sidebarToggle.addEventListener('click', () => {
|
|
1572
|
+
if (window.innerWidth <= 850) {
|
|
1573
|
+
const isOpen = sidebar.classList.contains('mobile-open');
|
|
1574
|
+
if (isOpen) {
|
|
1575
|
+
sidebar.classList.remove('mobile-open');
|
|
1576
|
+
sidebarOverlay.classList.remove('show');
|
|
1577
|
+
} else {
|
|
1578
|
+
sidebar.classList.add('mobile-open');
|
|
1579
|
+
sidebarOverlay.classList.add('show');
|
|
1580
|
+
}
|
|
1581
|
+
} else {
|
|
1582
|
+
sidebarVisible = !sidebarVisible;
|
|
1583
|
+
updateSidebarState();
|
|
1584
|
+
}
|
|
1585
|
+
});
|
|
1586
|
+
|
|
1587
|
+
sidebarOverlay.addEventListener('click', () => {
|
|
1588
|
+
sidebar.classList.remove('mobile-open');
|
|
1589
|
+
sidebarOverlay.classList.remove('show');
|
|
1590
|
+
});
|
|
1591
|
+
|
|
1592
|
+
window.addEventListener('resize', updateSidebarState);
|
|
1593
|
+
updateSidebarState();
|
|
1594
|
+
|
|
1595
|
+
// ===== NAVIGATION =====
|
|
1596
|
+
const sections = {
|
|
1597
|
+
overview: document.getElementById('overview'),
|
|
1598
|
+
'getting-started': document.getElementById('getting-started'),
|
|
1599
|
+
'server-class': document.getElementById('server-class'),
|
|
1600
|
+
routing: document.getElementById('routing'),
|
|
1601
|
+
middleware: document.getElementById('middleware'),
|
|
1602
|
+
'static-files': document.getElementById('static-files'),
|
|
1603
|
+
cookies: document.getElementById('cookies'),
|
|
1604
|
+
'admin-monitor': document.getElementById('admin-monitor'),
|
|
1605
|
+
types: document.getElementById('types')
|
|
1606
|
+
};
|
|
1607
|
+
|
|
1608
|
+
function switchSection(sectionId) {
|
|
1609
|
+
Object.values(sections).forEach(section => {
|
|
1610
|
+
if (section) {
|
|
1611
|
+
section.classList.add('hidden-section');
|
|
1612
|
+
section.classList.remove('section');
|
|
1613
|
+
}
|
|
1614
|
+
});
|
|
1615
|
+
const activeSection = sections[sectionId];
|
|
1616
|
+
if (activeSection) {
|
|
1617
|
+
activeSection.classList.remove('hidden-section');
|
|
1618
|
+
activeSection.classList.add('section');
|
|
1619
|
+
}
|
|
1620
|
+
navItems.forEach(btn => {
|
|
1621
|
+
btn.classList.remove('active');
|
|
1622
|
+
if (btn.dataset.section === sectionId) {
|
|
1623
|
+
btn.classList.add('active');
|
|
1624
|
+
}
|
|
1625
|
+
});
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
navItems.forEach(btn => {
|
|
1629
|
+
btn.addEventListener('click', (e) => {
|
|
1630
|
+
const section = e.currentTarget.dataset.section;
|
|
1631
|
+
if (section) {
|
|
1632
|
+
switchSection(section);
|
|
1633
|
+
if (window.innerWidth <= 850) {
|
|
1634
|
+
sidebar.classList.remove('mobile-open');
|
|
1635
|
+
sidebarOverlay.classList.remove('show');
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
});
|
|
1639
|
+
});
|
|
1640
|
+
|
|
1641
|
+
// ===== SYNTAX HIGHLIGHTING =====
|
|
1642
|
+
document.querySelectorAll('.code-block code').forEach((block) => {
|
|
1643
|
+
hljs.highlightElement(block);
|
|
1644
|
+
});
|
|
1645
|
+
|
|
1646
|
+
// Initial section
|
|
1647
|
+
switchSection('overview');
|
|
1648
|
+
})();
|
|
1649
|
+
</script>
|
|
1650
|
+
</body>
|
|
1651
|
+
</html>
|