lieko-express 0.0.1 → 0.0.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 ADDED
@@ -0,0 +1,1854 @@
1
+ # **Lieko-express — A Modern, Minimal, REST API Framework for Node.js**
2
+
3
+ A lightweight, fast, and modern Node.js REST API framework built on top of the native `http` module. Zero external dependencies for core functionality.
4
+
5
+ ![Performance](https://img.shields.io/badge/Performance-49%25_faster_than_Express-00d26a?style=for-the-badge)
6
+
7
+ ![Latency](https://img.shields.io/badge/Latency-67%25_lower_than_Express-00b4d8?style=for-the-badge)
8
+
9
+ ![Requests/sec](https://img.shields.io/badge/Requests-16.6k/sec-ff6b6b?style=for-the-badge)
10
+
11
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
12
+ [![Node.js Version](https://img.shields.io/badge/node-%3E%3D14.0.0-brightgreen)](https://nodejs.org)
13
+ [![npm version](https://img.shields.io/npm/v/lieko-express.svg)](https://www.npmjs.com/package/lieko-express)
14
+
15
+ [![GitHub](https://img.shields.io/badge/GitHub-Repository-black?logo=github)](https://github.com/eiwSrvt/lieko-express)
16
+ [![Discord](https://img.shields.io/discord/1399525160050102414?color=5865F2&logo=discord&logoColor=white)](https://discord.gg/EpgPqjvd)
17
+
18
+
19
+
20
+ | Metric | Express.js | Lieko Express | Improvement |
21
+ |--------|------------|---------------|-------------|
22
+ | **Max Throughput** | 13,303 req/sec | **16,644 req/sec** | **+25.1%** |
23
+ | **Best Case (POST)** | 11,202 req/sec | **16,644 req/sec** | **+48.6%** |
24
+ | **Worst Case (GET)** | 13,303 req/sec | **16,152 req/sec** | **+21.4%** |
25
+ | **Average Latency** | 30µs | **10µs** | **-66.7%** |
26
+ | **Total Requests** | 336k (POST) | **499k (POST)** | **+48.5%** |
27
+
28
+ **Key Takeaways:**
29
+ - ✅ **Up to 49% higher throughput** than Express.js
30
+ - ✅ **67% lower latency** (10µs vs 30µs)
31
+ - ✅ **Consistently outperforms** Express in all tests
32
+
33
+ ## ✨ Features
34
+
35
+ - 🎯 **Zero Dependencies** - Built entirely on Node.js native modules
36
+ - ⚡ **High Performance** - Optimized for speed with minimal overhead
37
+ - 🛡️ **Built-in Validation** - Comprehensive validation system with 15+ validators
38
+ - 🔄 **Router Support** - Modular routing with nested routers
39
+ - 🔐 **Middleware System** - Global and route-specific middlewares
40
+ - 📝 **Body Parsing** - JSON, URL-encoded, and multipart/form-data support
41
+ - 🎨 **Response Helpers** - Convenient methods for common responses
42
+ - 🔍 **Route Parameters** - Dynamic route matching with named parameters
43
+ - 🌐 **Query Parsing** - Automatic type conversion for query parameters
44
+ - 📤 **File Upload** - Built-in multipart/form-data handling
45
+ - ⚠️ **Error Handling** - Structured error responses with status codes
46
+
47
+
48
+ ## 📚 Table of Contents
49
+
50
+ - [Philosophy](#philosophy)
51
+ - [Basic Usage](#basic-usage)
52
+ - [Routing](#routing)
53
+ - [Middlewares](#middlewares)
54
+ - [Request & Response](#request--response)
55
+ - [Validation](#validation)
56
+ - [Routers](#routers)
57
+ - [Error Handling](#error-handling)
58
+ - [Advanced Examples](#advanced-examples)
59
+
60
+ ---
61
+
62
+ # 🧠 Philosophy
63
+
64
+ Lieko attempts to solve several common issues with classic Node frameworks:
65
+
66
+ ### ❌ Express problems:
67
+
68
+ * No route grouping
69
+ * Weak middlewares
70
+ * No validation
71
+ * Messy routers
72
+ * Confusing trust proxy behavior
73
+ * No helpers
74
+
75
+ ### ✔ Lieko solutions:
76
+
77
+ * Clean **group-based routing**
78
+ * Built-in validators
79
+ * Typed & normalized request fields
80
+ * Robust error system
81
+ * First-class JSON & uploads
82
+ * Simple, powerful helpers (res.ok, res.error…)
83
+ * Predictable IP handling
84
+
85
+ Lieko feels familiar but is dramatically more coherent and modern.
86
+
87
+
88
+
89
+ ## 📦 Installation
90
+
91
+ ```bash
92
+ npm install lieko-express
93
+ ```
94
+
95
+ ## 🚀 Quick Start
96
+
97
+ ```javascript
98
+ const Lieko = require('lieko-express');
99
+
100
+ const app = Lieko();
101
+
102
+ app.get('/', (req, res) => {
103
+ res.json({ message: 'Hello, Lieko!' });
104
+ });
105
+
106
+ app.listen(3000, () => {
107
+ console.log('Server running on port 3000');
108
+ });
109
+ ```
110
+
111
+ ## 🎯 Basic Usage
112
+
113
+ ### Creating an Application
114
+
115
+ ```javascript
116
+ const Lieko = require('lieko-express');
117
+ const app = Lieko();
118
+ ```
119
+
120
+ ### HTTP Methods
121
+
122
+ ```javascript
123
+ // GET request
124
+ app.get('/users', (req, res) => {
125
+ res.json({ users: [] });
126
+ });
127
+
128
+ // POST request
129
+ app.post('/users', (req, res) => {
130
+ const user = req.body;
131
+ res.status(201).json({ user });
132
+ });
133
+
134
+ // PUT request
135
+ app.put('/users/:id', (req, res) => {
136
+ const { id } = req.params;
137
+ res.json({ updated: true, id });
138
+ });
139
+
140
+ // DELETE request
141
+ app.delete('/users/:id', (req, res) => {
142
+ res.json({ deleted: true });
143
+ });
144
+
145
+ // PATCH request
146
+ app.patch('/users/:id', (req, res) => {
147
+ res.json({ patched: true });
148
+ });
149
+
150
+ // ALL methods
151
+ app.all('/admin', (req, res) => {
152
+ res.json({ method: req.method });
153
+ });
154
+ ```
155
+
156
+
157
+ # 🛣 Routing
158
+
159
+ Lieko provides classic `.get()`, `.post()`, `.patch()`, `.delete()`:
160
+
161
+ ```js
162
+ app.get('/hello', (req, res) => res.ok('Hello!'));
163
+ app.post('/upload', uploadFile);
164
+ app.patch('/users/:id', updateUser);
165
+ app.delete('/posts/:id', deletePost);
166
+ ```
167
+
168
+ ### Features
169
+
170
+ ✔ Params automatically extracted
171
+
172
+ ✔ Query auto-typed
173
+
174
+ ✔ Body parsed and typed
175
+
176
+ ✔ Wildcards available
177
+
178
+ ✔ Trailing slashes handled intelligently
179
+
180
+ ---
181
+
182
+ # 🧩 Route Groups
183
+
184
+ Lieko supports Laravel-style **group routing** with middleware inheritance:
185
+
186
+ ```js
187
+ // (api) = name of subroutes
188
+ app.group('/api', auth, (api) => {
189
+
190
+ api.get('/profile', (req, res) =>
191
+ res.ok({ user: req.user })
192
+ );
193
+
194
+ api.group('/admin', requireAdmin, (admin) => {
195
+
196
+ admin.get('/stats', (req, res) =>
197
+ res.ok({ admin: true })
198
+ );
199
+
200
+ });
201
+
202
+ });
203
+ ```
204
+
205
+ ### Group behavior
206
+
207
+ * Middlewares inherited by all children
208
+ * Prefix applied recursively
209
+ * You can nest indefinitely
210
+ * Works inside routers too
211
+
212
+
213
+
214
+ # 📦 Nested Routers
215
+
216
+ Routers are fully nestable:
217
+
218
+ ```js
219
+ const { Router } = require('lieko-express');
220
+
221
+ const app = Router();
222
+
223
+ app.get('/', listUsers);
224
+ app.post('/', createUser);
225
+ app.get('/:id', getUser);
226
+
227
+ app.group('/api', auth, (api) => {
228
+ api.group('/admin', requireAdmin, (admin) => {
229
+ admin.use('/users', users);
230
+ });
231
+ });
232
+
233
+ // Group without TOP middleware
234
+ app.group('/api', (api) => {
235
+ api.group('/admin', requireAdmin, (admin) => {
236
+ admin.use('/users', users);
237
+ });
238
+ });
239
+
240
+ app.group('/admin2', auth, requireAdmin, rateLimit, (admin) => {
241
+ admin.get('/stats', getStats);
242
+ });
243
+
244
+ api.group(
245
+ '/secure',
246
+ requireAuth(config),
247
+ requirePermissions(['users.read', 'posts.write']),
248
+ requireAdmin('super'),
249
+ (secure) => {
250
+ secure.get('/panel', ...);
251
+ }
252
+ );
253
+ ```
254
+ ✔ Router inherits middleware from its parent groups
255
+
256
+ ✔ Paths automatically expanded
257
+
258
+ ✔ Perfect for modular architecture
259
+
260
+
261
+ # 🧩 API Versioning
262
+
263
+ With groups, versioning becomes trivial:
264
+
265
+ ```js
266
+ app.group('/api/v1', (v1) => {
267
+ v1.get('/users', handlerV1);
268
+ });
269
+
270
+ app.group('/api/v2', (v2) => {
271
+ v2.get('/users', handlerV2);
272
+ });
273
+ ```
274
+
275
+
276
+ ### Query Parameters
277
+
278
+ Query parameters are automatically parsed and converted to appropriate types:
279
+
280
+ ```javascript
281
+ app.get('/search', (req, res) => {
282
+ // GET /search?q=hello&page=2&active=true&price=19.99
283
+
284
+ console.log(req.query);
285
+ // {
286
+ // q: 'hello', // string
287
+ // page: 2, // number (auto-converted)
288
+ // active: true, // boolean (auto-converted)
289
+ // price: 19.99 // float (auto-converted)
290
+ // }
291
+
292
+ res.json({ query: req.query });
293
+ });
294
+ ```
295
+
296
+ **Automatic Type Conversion:**
297
+ - `"123"` → `123` (number)
298
+ - `"19.99"` → `19.99` (float)
299
+ - `"true"` → `true` (boolean)
300
+ - `"false"` → `false` (boolean)
301
+
302
+
303
+ ## 🔧 Middlewares
304
+
305
+ ### Global Middlewares
306
+
307
+ ```javascript
308
+ // Logger middleware
309
+ app.use((req, res, next) => {
310
+ console.log(`${req.method} ${req.url}`);
311
+ next();
312
+ });
313
+
314
+ // CORS middleware
315
+ app.use((req, res, next) => {
316
+ res.set('Access-Control-Allow-Origin', '*');
317
+ res.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
318
+ next();
319
+ });
320
+
321
+ // Auth middleware
322
+ const authMiddleware = (req, res, next) => {
323
+ const token = req.headers['authorization'];
324
+
325
+ if (!token) {
326
+ return res.error({
327
+ code: 'NO_TOKEN_PROVIDED',
328
+ message: 'Authorization required'
329
+ });
330
+ }
331
+
332
+ req.user = { id: 1, role: 'admin' };
333
+ next();
334
+ };
335
+ ```
336
+
337
+ ### Path-Specific Middlewares
338
+
339
+ ```javascript
340
+ // Apply to specific path
341
+ app.use('/api', (req, res, next) => {
342
+ console.log('API route accessed');
343
+ next();
344
+ });
345
+
346
+ // Multiple middlewares
347
+ app.use('/admin', authMiddleware, checkRole, (req, res, next) => {
348
+ next();
349
+ });
350
+ ```
351
+
352
+ ### Route-Level Middlewares
353
+
354
+ ```javascript
355
+ // Single middleware
356
+ app.get('/protected', authMiddleware, (req, res) => {
357
+ res.json({ user: req.user });
358
+ });
359
+
360
+ // Multiple middlewares
361
+ app.post('/admin/users',
362
+ authMiddleware,
363
+ checkRole('admin'),
364
+ validate(userSchema),
365
+ (req, res) => {
366
+ res.json({ created: true });
367
+ }
368
+ );
369
+ ```
370
+
371
+ ### Async Middlewares
372
+
373
+ ```javascript
374
+ // Async/await support
375
+ app.use(async (req, res, next) => {
376
+ try {
377
+ req.data = await fetchDataFromDB();
378
+ next();
379
+ } catch (error) {
380
+ next(error);
381
+ }
382
+ });
383
+ ```
384
+
385
+
386
+ # 🔍 Request Object
387
+
388
+ Lieko’s `req` object provides:
389
+
390
+ ### General
391
+
392
+ | Field | Description |
393
+ | ----------------- | ----------------- |
394
+ | `req.method` | HTTP method |
395
+ | `req.path` | Without query |
396
+ | `req.originalUrl` | Full original URL |
397
+ | `req.protocol` | `http` or `https` |
398
+ | `req.secure` | Boolean |
399
+ | `req.hostname` | Hostname |
400
+ | `req.subdomains` | Array |
401
+ | `req.xhr` | AJAX detection |
402
+ | `req.params` | Route parameters |
403
+ | `req.query` | Auto-typed query |
404
+ | `req.body` | Parsed body |
405
+ | `req.files` | Uploaded files |
406
+
407
+ ### IP Handling
408
+
409
+ | Field | Description |
410
+ | ------------- | ---------------------- |
411
+ | `req.ip.raw` | Original IP |
412
+ | `req.ip.ipv4` | IPv4 (auto normalized) |
413
+ | `req.ip.ipv6` | IPv6 |
414
+ | `req.ips` | Proxy chain |
415
+
416
+ Lieko safely handles:
417
+
418
+ * IPv4
419
+ * IPv6
420
+ * IPv4-mapped IPv6
421
+ * Multiple proxies
422
+
423
+
424
+ # 🎯 Response Object (`res`)
425
+
426
+ Lieko enhances Node's native response object with convenient helper methods.
427
+
428
+ ### **General Response Methods**
429
+
430
+ | Method / Field | Description |
431
+ | -------------------------- | ---------------------------------------- |
432
+ | `res.status(code)` | Sets HTTP status code (chainable). |
433
+ | `res.setHeader(name, val)` | Sets a response header. |
434
+ | `res.getHeader(name)` | Gets a response header. |
435
+ | `res.removeHeader(name)` | Removes a header. |
436
+ | `res.type(mime)` | Sets `Content-Type` automatically. |
437
+ | `res.send(data)` | Sends raw data (string, buffer, object). |
438
+ | `res.json(obj)` | Sends JSON with correct headers. |
439
+ | `res.end(body)` | Ends the response manually. |
440
+
441
+
442
+ # ✅ **High-Level Helpers**
443
+
444
+ ### **Success Helpers**
445
+
446
+ | Method | Description |
447
+ | ------------------------ | -------------------------------------------------- |
448
+ | `res.ok(data)` | Sends `{ success: true, data }` with status `200`. |
449
+ | `res.created(data)` | Sends `{ success: true, data }` with status `201`. |
450
+ | `res.noContent()` | Sends status `204` with no body. |
451
+ | `res.paginated(payload)` | Standard API pagination output. |
452
+
453
+
454
+ # ❌ **Error Helpers**
455
+
456
+ | Method | Description |
457
+ | ----------------------- | ------------------------------------------------- |
458
+ | `res.error(codeOrObj)` | Sends a normalized JSON error response (400–500). |
459
+ | `res.badRequest(msg)` | Sends `400 Bad Request`. |
460
+ | `res.unauthorized(msg)` | Sends `401 Unauthorized`. |
461
+ | `res.forbidden(msg)` | Sends `403 Forbidden`. |
462
+ | `res.notFound(msg)` | Sends `404 Not Found`. |
463
+ | `res.serverError(msg)` | Sends `500 Server Error`. |
464
+
465
+ > All these helpers output a consistent JSON error structure:
466
+ >
467
+ > ```json
468
+ > { "success": false, "error": { "code": "...", "message": "..." } }
469
+ > ```
470
+
471
+ # 🧠 **Content-Type Helpers**
472
+
473
+ ### **HTML**
474
+
475
+ | Method | Description |
476
+ | ----------------------------- | ---------------------------------------------------------- |
477
+ | `res.html(html, status?)` | Short alias for `sendHTML()`. | |
478
+
479
+ ### **Redirects**
480
+
481
+ | Method | Description |
482
+ | -------------------------- | ------------------------------------- |
483
+ | `res.redirect(url, code?)` | Redirects the client (default `302`). |
484
+
485
+
486
+ # 📦 **Low-Level Output Controls**
487
+
488
+ | Method | Description |
489
+ | ---------------------------------- | ----------------------------------------------- |
490
+ | `res.write(chunk)` | Writes a raw chunk without ending the response. |
491
+ | `res.flushHeaders()` | Sends the headers immediately. |
492
+
493
+
494
+ ### JSON helpers
495
+
496
+ ```js
497
+ res.ok(data);
498
+ res.created(data);
499
+ res.accepted(data);
500
+ res.noContent();
501
+ res.error({ code: "INVALID_DATA" });
502
+ ```
503
+
504
+ ### Pagination helper
505
+
506
+ ```js
507
+ res.paginated(items, page, limit, total);
508
+ ```
509
+
510
+ ### Response formatting
511
+
512
+ * Uniform JSON structure
513
+ * Automatic status codes
514
+ * Error code → HTTP mapping
515
+ * String errors also supported (`res.error("Invalid user")`)
516
+
517
+ # 📦 Body Parsing
518
+
519
+ Lieko supports:
520
+
521
+ ### ✔ JSON
522
+
523
+ ### ✔ URL-encoded
524
+
525
+ ### ✔ Multipart form-data (files)
526
+
527
+ Uploads end up in:
528
+
529
+ ```
530
+ req.files = {
531
+ avatar: {
532
+ filename: "...",
533
+ mimetype: "...",
534
+ size: 1234,
535
+ buffer: <Buffer>
536
+ }
537
+ }
538
+ ```
539
+
540
+ Query & body fields are **auto converted**:
541
+
542
+ ```
543
+ "123" → 123
544
+ "true" → true
545
+ "false" → false
546
+ "null" → null
547
+ ```
548
+
549
+ ### Response Methods
550
+
551
+ ```javascript
552
+ // JSON response
553
+ res.json({ message: 'Hello' });
554
+
555
+ // Send any data
556
+ res.send('Plain text');
557
+ res.send({ object: 'data' });
558
+
559
+ // Status code
560
+ res.status(404).json({ error: 'Not found' });
561
+
562
+ // Set headers
563
+ res.set('X-Custom-Header', 'value');
564
+ res.header('Content-Type', 'text/html');
565
+
566
+ // Redirect
567
+ res.redirect('/new-url');
568
+
569
+ // Success response helper
570
+ res.ok({ user: userData }, 'User retrieved successfully');
571
+ // Returns: { success: true, data: userData, message: '...' }
572
+
573
+ // Error response helper
574
+ res.error({
575
+ code: 'NOT_FOUND',
576
+ message: 'Resource not found'
577
+ });
578
+ // Returns: { success: false, error: { code, message } }
579
+
580
+ // Response locals (shared data)
581
+ res.locals.user = currentUser;
582
+ ```
583
+
584
+ ### Body Parsing
585
+
586
+ **Automatic parsing for:**
587
+
588
+ 1. **JSON** (`application/json`)
589
+ ```javascript
590
+ app.post('/api/data', (req, res) => {
591
+ console.log(req.body); // { name: 'John', age: 30 }
592
+ res.json({ received: true });
593
+ });
594
+ ```
595
+
596
+ 2. **URL-encoded** (`application/x-www-form-urlencoded`)
597
+ ```javascript
598
+ app.post('/form', (req, res) => {
599
+ console.log(req.body); // { username: 'john', password: '***' }
600
+ res.json({ ok: true });
601
+ });
602
+ ```
603
+
604
+ 3. **Multipart/form-data** (file uploads)
605
+ ```javascript
606
+ app.post('/upload', (req, res) => {
607
+ const file = req.files.avatar;
608
+
609
+ console.log(file.filename); // 'profile.jpg'
610
+ console.log(file.contentType); // 'image/jpeg'
611
+ console.log(file.data); // Buffer
612
+ console.log(file.data.length); // File size in bytes
613
+
614
+ // Other form fields
615
+ console.log(req.body.username); // 'john'
616
+
617
+ res.json({ uploaded: true });
618
+ });
619
+ ```
620
+
621
+ ## ✅ Validation
622
+
623
+ ### Built-in Validators
624
+
625
+ ```javascript
626
+ const { schema, validators, validate } = require('lieko-express');
627
+
628
+ const userSchema = schema({
629
+ // Required field
630
+ username: [
631
+ validators.required('Username is required'),
632
+ validators.string(),
633
+ validators.minLength(3),
634
+ validators.maxLength(20),
635
+ validators.pattern(/^[a-zA-Z0-9_]+$/, 'Alphanumeric only')
636
+ ],
637
+
638
+ // Email validation
639
+ email: [
640
+ validators.required(),
641
+ validators.email('Invalid email format')
642
+ ],
643
+
644
+ // Number validation
645
+ age: [
646
+ validators.required(),
647
+ validators.number(),
648
+ validators.min(18, 'Must be 18 or older'),
649
+ validators.max(120)
650
+ ],
651
+
652
+ // Boolean validation
653
+ active: [
654
+ validators.boolean()
655
+ ],
656
+
657
+ // Must be true (for terms acceptance)
658
+ acceptTerms: [
659
+ validators.required(),
660
+ validators.mustBeTrue('You must accept terms')
661
+ ],
662
+
663
+ // Enum validation
664
+ role: [
665
+ validators.oneOf(['user', 'admin', 'moderator'])
666
+ ],
667
+
668
+ // Custom validator
669
+ password: [
670
+ validators.required(),
671
+ validators.custom((value) => {
672
+ return value.length >= 8 && /[A-Z]/.test(value);
673
+ }, 'Password must be 8+ chars with uppercase')
674
+ ],
675
+
676
+ // Equal to another value
677
+ confirmPassword: [
678
+ validators.equal(req => req.body.password, 'Passwords must match')
679
+ ]
680
+ });
681
+
682
+ // Use in route
683
+ app.post('/register', validate(userSchema), (req, res) => {
684
+ // If validation passes, this runs
685
+ res.status(201).json({ success: true });
686
+ });
687
+ ```
688
+
689
+ ### All Available Validators
690
+
691
+ | Validator | Description | Example |
692
+ | ------------------------------ | --------------------------------------------------- | ---------------------------------------- |
693
+ | `required(message?)` | Field must be present (not null/empty) | `validators.required()` |
694
+ | `requiredTrue(message?)` | Must be true (accepts `true`, `"true"`, `1`, `"1"`) | `validators.requiredTrue()` |
695
+ | `optional()` | Skip validation if field is missing | `validators.optional()` |
696
+ | `string(message?)` | Must be a string | `validators.string()` |
697
+ | `number(message?)` | Must be a number (no coercion) | `validators.number()` |
698
+ | `boolean(message?)` | Must be boolean-like (`true/false`, `"1"/"0"`) | `validators.boolean()` |
699
+ | `integer(message?)` | Must be an integer | `validators.integer()` |
700
+ | `positive(message?)` | Must be > 0 | `validators.positive()` |
701
+ | `negative(message?)` | Must be < 0 | `validators.negative()` |
702
+ | `email(message?)` | Must be a valid email | `validators.email()` |
703
+ | `min(value, message?)` | Minimum number or string length | `validators.min(3)` |
704
+ | `max(value, message?)` | Maximum number or string length | `validators.max(10)` |
705
+ | `length(n, message?)` | Exact string length | `validators.length(6)` |
706
+ | `minLength(n, message?)` | Minimum string length | `validators.minLength(3)` |
707
+ | `maxLength(n, message?)` | Maximum string length | `validators.maxLength(255)` |
708
+ | `pattern(regex, message?)` | Must match regex | `validators.pattern(/^\d+$/)` |
709
+ | `startsWith(prefix, message?)` | Must start with prefix | `validators.startsWith("abc")` |
710
+ | `endsWith(suffix, message?)` | Must end with suffix | `validators.endsWith(".jpg")` |
711
+ | `oneOf(values, message?)` | Value must be in list | `validators.oneOf(['admin','user'])` |
712
+ | `notOneOf(values, message?)` | Value cannot be in list | `validators.notOneOf(['root','system'])` |
713
+ | `custom(fn, message?)` | Custom validation | `validators.custom(val => val > 0)` |
714
+ | `equal(value, message?)` | Must equal specific value | `validators.equal("yes")` |
715
+ | `mustBeTrue(message?)` | Must be true (alias of requiredTrue) | `validators.mustBeTrue()` |
716
+ | `mustBeFalse(message?)` | Must be false | `validators.mustBeFalse()` |
717
+ | `date(message?)` | Must be valid date | `validators.date()` |
718
+ | `before(date, message?)` | Must be < given date | `validators.before("2025-01-01")` |
719
+ | `after(date, message?)` | Must be > given date | `validators.after("2020-01-01")` |
720
+
721
+ ## **Basic Schema Example**
722
+
723
+ ```js
724
+ const userSchema = schema({
725
+ name: [
726
+ validators.required(),
727
+ validators.string(),
728
+ validators.minLength(3),
729
+ ],
730
+ age: [
731
+ validators.required(),
732
+ validators.number(),
733
+ validators.min(18),
734
+ ],
735
+ });
736
+ ```
737
+
738
+ ## **Boolean Example**
739
+
740
+ ```js
741
+ const schema = schema({
742
+ subscribed: [
743
+ validators.boolean()
744
+ ]
745
+ });
746
+ ```
747
+
748
+ Accepted:
749
+
750
+ ```
751
+ true, false, "true", "false", 1, 0, "1", "0"
752
+ ```
753
+
754
+ ## **Email Example**
755
+
756
+ ```js
757
+ const schema = schema({
758
+ email: [
759
+ validators.required(),
760
+ validators.email()
761
+ ]
762
+ });
763
+ ```
764
+
765
+ ## **Username with rules**
766
+
767
+ ```js
768
+ const usernameSchema = schema({
769
+ username: [
770
+ validators.required(),
771
+ validators.string(),
772
+ validators.minLength(3),
773
+ validators.maxLength(16),
774
+ validators.pattern(/^[a-zA-Z0-9_]+$/, "Invalid username")
775
+ ]
776
+ });
777
+ ```
778
+
779
+ ## **Password strength**
780
+
781
+ ```js
782
+ const passwordSchema = schema({
783
+ password: [
784
+ validators.required(),
785
+ validators.minLength(8),
786
+ validators.custom((value) => {
787
+ return /[A-Z]/.test(value) &&
788
+ /[a-z]/.test(value) &&
789
+ /\d/.test(value) &&
790
+ /[!@#$%^&*]/.test(value);
791
+ }, "Weak password")
792
+ ]
793
+ });
794
+ ```
795
+
796
+ ## **Multiple-choice fields**
797
+
798
+ ```js
799
+ const roleSchema = schema({
800
+ role: [
801
+ validators.oneOf(["admin", "user", "guest"])
802
+ ]
803
+ });
804
+ ```
805
+
806
+
807
+ ## **Blacklist Example**
808
+
809
+ ```js
810
+ const schema = schema({
811
+ username: [
812
+ validators.notOneOf(["root", "admin", "system"])
813
+ ]
814
+ });
815
+ ```
816
+
817
+ ## **Starts / Ends With**
818
+
819
+ ```js
820
+ const schema = schema({
821
+ fileName: [
822
+ validators.endsWith(".jpg", "Must be a JPG image")
823
+ ]
824
+ });
825
+ ```
826
+
827
+ # **Advanced Validation Examples**
828
+
829
+
830
+
831
+ ## **Cross-field validation (matching passwords)**
832
+
833
+ ```js
834
+ const registerSchema = schema({
835
+ password: [
836
+ validators.required(),
837
+ validators.minLength(8)
838
+ ],
839
+ confirmPassword: [
840
+ validators.required(),
841
+ validators.custom((value, data) => value === data.password, "Passwords do not match")
842
+ ]
843
+ });
844
+ ```
845
+
846
+
847
+ ## Conditional Validation (depends on another field)
848
+
849
+ ```js
850
+ const orderSchema = schema({
851
+ shippingMethod: [validators.oneOf(["pickup", "delivery"])],
852
+ address: [
853
+ validators.custom((value, data) => {
854
+ if (data.shippingMethod === "delivery")
855
+ return value && value.length > 0;
856
+ return true;
857
+ }, "Address required when using delivery")
858
+ ]
859
+ });
860
+ ```
861
+
862
+
863
+ ## Dynamic rules EX: `age` required only if user is not admin
864
+
865
+ ```js
866
+ const schema = schema({
867
+ role: [validators.oneOf(["user", "admin"])],
868
+ age: [
869
+ validators.custom((age, data) => {
870
+ if (data.role === "user") return age >= 18;
871
+ return true;
872
+ }, "Users must be 18+")
873
+ ]
874
+ });
875
+ ```
876
+
877
+
878
+ ## **Date validation**
879
+
880
+ ```js
881
+ const eventSchema = schema({
882
+ startDate: [validators.date()],
883
+ endDate: [
884
+ validators.date(),
885
+ validators.custom((value, data) => {
886
+ return new Date(value) > new Date(data.startDate);
887
+ }, "End date must be after start date")
888
+ ]
889
+ });
890
+ ```
891
+
892
+
893
+ # **Combining many validators**
894
+
895
+ ```js
896
+ const schema = schema({
897
+ code: [
898
+ validators.required(),
899
+ validators.string(),
900
+ validators.length(6),
901
+ validators.pattern(/^[A-Z0-9]+$/),
902
+ validators.notOneOf(["AAAAAA", "000000"]),
903
+ ]
904
+ });
905
+ ```
906
+
907
+
908
+ # **Optional field + rules if provided**
909
+
910
+ ```js
911
+ const schema = schema({
912
+ nickname: [
913
+ validators.optional(),
914
+ validators.minLength(3),
915
+ validators.maxLength(20)
916
+ ]
917
+ });
918
+ ```
919
+
920
+ If nickname is empty → no validation.
921
+ If present → must follow rules.
922
+
923
+
924
+ # **Example of validation error response**
925
+
926
+ Already provided but I format it more "realistic":
927
+
928
+ ```json
929
+ {
930
+ "success": false,
931
+ "message": "Validation failed",
932
+ "errors": [
933
+ {
934
+ "field": "email",
935
+ "message": "Invalid email format",
936
+ "type": "email"
937
+ },
938
+ {
939
+ "field": "age",
940
+ "message": "Field must be at least 18",
941
+ "type": "min"
942
+ }
943
+ ]
944
+ }
945
+ ```
946
+
947
+
948
+ ### Custom Validation Examples
949
+
950
+ ```javascript
951
+ // Password strength
952
+ const passwordSchema = schema({
953
+ password: [
954
+ validators.required(),
955
+ validators.custom((value) => {
956
+ const hasUpperCase = /[A-Z]/.test(value);
957
+ const hasLowerCase = /[a-z]/.test(value);
958
+ const hasNumbers = /\d/.test(value);
959
+ const hasSpecialChar = /[!@#$%^&*]/.test(value);
960
+
961
+ return hasUpperCase && hasLowerCase && hasNumbers && hasSpecialChar;
962
+ }, 'Password must contain uppercase, lowercase, number and special char')
963
+ ]
964
+ });
965
+
966
+ // Cross-field validation
967
+ const registrationSchema = schema({
968
+ password: [validators.required(), validators.minLength(8)],
969
+ confirmPassword: [
970
+ validators.required(),
971
+ validators.custom((value, data) => {
972
+ return value === data.password;
973
+ }, 'Passwords do not match')
974
+ ]
975
+ });
976
+
977
+ // Conditional validation
978
+ const orderSchema = schema({
979
+ shippingAddress: [
980
+ validators.custom((value, data) => {
981
+ // Only required if shipping method is 'delivery'
982
+ if (data.shippingMethod === 'delivery') {
983
+ return value && value.length > 0;
984
+ }
985
+ return true;
986
+ }, 'Shipping address required for delivery')
987
+ ]
988
+ });
989
+ ```
990
+
991
+
992
+ ## 🗂️ Routers
993
+
994
+ Routers allow you to create modular, mountable route handlers.
995
+
996
+ ### Creating a Router
997
+
998
+ ```javascript
999
+ const Lieko = require('lieko-express');
1000
+ const usersRouter = Lieko.Router();
1001
+
1002
+ // Define routes on the router
1003
+ usersRouter.get('/', (req, res) => {
1004
+ res.json({ users: [] });
1005
+ });
1006
+
1007
+ usersRouter.get('/:id', (req, res) => {
1008
+ res.json({ user: { id: req.params.id } });
1009
+ });
1010
+
1011
+ usersRouter.post('/', (req, res) => {
1012
+ res.status(201).json({ created: true });
1013
+ });
1014
+
1015
+ // Mount the router
1016
+ const app = Lieko();
1017
+ app.use('/api/users', usersRouter);
1018
+
1019
+ // Now accessible at:
1020
+ // GET /api/users
1021
+ // GET /api/users/:id
1022
+ // POST /api/users
1023
+ ```
1024
+
1025
+ ### Nested Routers
1026
+
1027
+ ```javascript
1028
+ const apiRouter = Lieko.Router();
1029
+ const usersRouter = Lieko.Router();
1030
+ const postsRouter = Lieko.Router();
1031
+
1032
+ // Users routes
1033
+ usersRouter.get('/', (req, res) => res.json({ users: [] }));
1034
+ usersRouter.get('/:id', (req, res) => res.json({ user: {} }));
1035
+
1036
+ // Posts routes
1037
+ postsRouter.get('/', (req, res) => res.json({ posts: [] }));
1038
+ postsRouter.get('/:id', (req, res) => res.json({ post: {} }));
1039
+
1040
+ // Mount sub-routers on API router
1041
+ apiRouter.use('/users', usersRouter);
1042
+ apiRouter.use('/posts', postsRouter);
1043
+
1044
+ // Mount API router on app
1045
+ app.use('/api', apiRouter);
1046
+
1047
+ // Available routes:
1048
+ // /api/users
1049
+ // /api/users/:id
1050
+ // /api/posts
1051
+ // /api/posts/:id
1052
+ ```
1053
+
1054
+ ### Router with Middlewares
1055
+
1056
+ ```javascript
1057
+ const adminRouter = Lieko.Router();
1058
+
1059
+ // Middleware for all admin routes
1060
+ const adminAuth = (req, res, next) => {
1061
+ if (req.user && req.user.role === 'admin') {
1062
+ next();
1063
+ } else {
1064
+ res.error({ code: 'FORBIDDEN', message: 'Admin access required' });
1065
+ }
1066
+ };
1067
+
1068
+ adminRouter.get('/dashboard', (req, res) => {
1069
+ res.json({ dashboard: 'data' });
1070
+ });
1071
+
1072
+ adminRouter.post('/settings', (req, res) => {
1073
+ res.json({ updated: true });
1074
+ });
1075
+
1076
+ // Mount with middleware
1077
+ app.use('/admin', authMiddleware, adminAuth, adminRouter);
1078
+ ```
1079
+
1080
+ ### Modular Application Structure
1081
+
1082
+ ```javascript
1083
+ // routes/users.js
1084
+ const Lieko = require('lieko-express');
1085
+ const router = Lieko.Router();
1086
+
1087
+ router.get('/', (req, res) => { /* ... */ });
1088
+ router.post('/', (req, res) => { /* ... */ });
1089
+
1090
+ module.exports = router;
1091
+
1092
+ // routes/posts.js
1093
+ const Lieko = require('lieko-express');
1094
+ const router = Lieko.Router();
1095
+
1096
+ router.get('/', (req, res) => { /* ... */ });
1097
+ router.post('/', (req, res) => { /* ... */ });
1098
+
1099
+ module.exports = router;
1100
+
1101
+ // app.js
1102
+ const Lieko = require('lieko-express');
1103
+ const usersRouter = require('./routes/users');
1104
+ const postsRouter = require('./routes/posts');
1105
+
1106
+ const app = Lieko();
1107
+
1108
+ app.use('/api/users', usersRouter);
1109
+ app.use('/api/posts', postsRouter);
1110
+
1111
+ app.listen(3000);
1112
+ ```
1113
+
1114
+ ## ⚠️ Error Handling
1115
+
1116
+ ### Custom 404 Handler
1117
+
1118
+ ```javascript
1119
+ app.notFound((req, res) => {
1120
+ res.status(404).json({
1121
+ error: 'Not Found',
1122
+ message: `Route ${req.method} ${req.url} does not exist`,
1123
+ timestamp: new Date().toISOString()
1124
+ });
1125
+ });
1126
+ ```
1127
+
1128
+ ### Error Response Helper
1129
+
1130
+ ```javascript
1131
+ app.get('/users/:id', (req, res) => {
1132
+ const user = findUser(req.params.id);
1133
+
1134
+ if (!user) {
1135
+ return res.error({
1136
+ code: 'NOT_FOUND',
1137
+ message: 'User not found'
1138
+ });
1139
+ }
1140
+
1141
+ res.json({ user });
1142
+ });
1143
+ ```
1144
+
1145
+ ### Standard Error Codes
1146
+
1147
+ The `res.error()` helper automatically maps error codes to HTTP status codes:
1148
+
1149
+ ```javascript
1150
+ // 4xx - Client Errors
1151
+ res.error({ code: 'INVALID_REQUEST' }); // 400
1152
+ res.error({ code: 'VALIDATION_FAILED' }); // 400
1153
+ res.error({ code: 'NO_TOKEN_PROVIDED' }); // 401
1154
+ res.error({ code: 'INVALID_TOKEN' }); // 401
1155
+ res.error({ code: 'FORBIDDEN' }); // 403
1156
+ res.error({ code: 'NOT_FOUND' }); // 404
1157
+ res.error({ code: 'METHOD_NOT_ALLOWED' }); // 405
1158
+ res.error({ code: 'CONFLICT' }); // 409
1159
+ res.error({ code: 'RECORD_EXISTS' }); // 409
1160
+ res.error({ code: 'TOO_MANY_REQUESTS' }); // 429
1161
+
1162
+ // 5xx - Server Errors
1163
+ res.error({ code: 'SERVER_ERROR' }); // 500
1164
+ res.error({ code: 'SERVICE_UNAVAILABLE' }); // 503
1165
+
1166
+ // Custom status
1167
+ res.error({ code: 'CUSTOM_ERROR', status: 418 });
1168
+ ```
1169
+
1170
+ ### Error Response Format
1171
+
1172
+ ```json
1173
+ {
1174
+ "success": false,
1175
+ "error": {
1176
+ "code": "NOT_FOUND",
1177
+ "message": "User not found"
1178
+ }
1179
+ }
1180
+ ```
1181
+
1182
+
1183
+ ## 🔥 Advanced Examples
1184
+
1185
+ ### Complete REST API
1186
+
1187
+ ```javascript
1188
+ const Lieko = require('lieko-express');
1189
+ const { Schema, validators, validate } = require('lieko-express');
1190
+
1191
+ const app = Lieko();
1192
+
1193
+ // Database (in-memory)
1194
+ const db = {
1195
+ users: [
1196
+ { id: 1, name: 'John Doe', email: 'john@example.com' }
1197
+ ]
1198
+ };
1199
+
1200
+ let nextId = 2;
1201
+
1202
+ // Validation schema
1203
+ const userSchema = schema({
1204
+ name: [
1205
+ validators.required('Name is required'),
1206
+ validators.minLength(2, 'Name must be at least 2 characters')
1207
+ ],
1208
+ email: [
1209
+ validators.required('Email is required'),
1210
+ validators.email('Invalid email format')
1211
+ ]
1212
+ });
1213
+
1214
+ // Middlewares
1215
+ app.use((req, res, next) => {
1216
+ console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
1217
+ next();
1218
+ });
1219
+
1220
+ // Routes
1221
+ app.get('/api/users', (req, res) => {
1222
+ res.ok(db.users, 'Users retrieved successfully');
1223
+ });
1224
+
1225
+ app.get('/api/users/:id', (req, res) => {
1226
+ const user = db.users.find(u => u.id === parseInt(req.params.id));
1227
+
1228
+ if (!user) {
1229
+ return res.error({ code: 'NOT_FOUND', message: 'User not found' });
1230
+ }
1231
+
1232
+ res.ok(user);
1233
+ });
1234
+
1235
+ app.post('/api/users', validate(userSchema), (req, res) => {
1236
+ const { name, email } = req.body;
1237
+
1238
+ // Check if email exists
1239
+ const exists = db.users.some(u => u.email === email);
1240
+ if (exists) {
1241
+ return res.error({
1242
+ code: 'RECORD_EXISTS',
1243
+ message: 'Email already registered'
1244
+ });
1245
+ }
1246
+
1247
+ const newUser = {
1248
+ id: nextId++,
1249
+ name,
1250
+ email
1251
+ };
1252
+
1253
+ db.users.push(newUser);
1254
+ res.status(201).ok(newUser, 'User created successfully');
1255
+ });
1256
+
1257
+ app.put('/api/users/:id', validate(userSchema), (req, res) => {
1258
+ const id = parseInt(req.params.id);
1259
+ const index = db.users.findIndex(u => u.id === id);
1260
+
1261
+ if (index === -1) {
1262
+ return res.error({ code: 'NOT_FOUND', message: 'User not found' });
1263
+ }
1264
+
1265
+ db.users[index] = { ...db.users[index], ...req.body, id };
1266
+ res.ok(db.users[index], 'User updated successfully');
1267
+ });
1268
+
1269
+ app.delete('/api/users/:id', (req, res) => {
1270
+ const id = parseInt(req.params.id);
1271
+ const index = db.users.findIndex(u => u.id === id);
1272
+
1273
+ if (index === -1) {
1274
+ return res.error({ code: 'NOT_FOUND', message: 'User not found' });
1275
+ }
1276
+
1277
+ db.users.splice(index, 1);
1278
+ res.ok({ deleted: true }, 'User deleted successfully');
1279
+ });
1280
+
1281
+ // 404 handler
1282
+ app.notFound((req, res) => {
1283
+ res.status(404).json({
1284
+ success: false,
1285
+ error: {
1286
+ code: 'NOT_FOUND',
1287
+ message: `Route ${req.method} ${req.url} not found`
1288
+ }
1289
+ });
1290
+ });
1291
+
1292
+ app.listen(3000, () => {
1293
+ console.log('🚀 Server running on http://localhost:3000');
1294
+ });
1295
+ ```
1296
+
1297
+ ### Authentication & Authorization
1298
+
1299
+ ```javascript
1300
+ const Lieko = require('lieko-express');
1301
+ const app = Lieko();
1302
+
1303
+ // Mock user database
1304
+ const users = [
1305
+ { id: 1, username: 'admin', password: 'admin123', role: 'admin' },
1306
+ { id: 2, username: 'user', password: 'user123', role: 'user' }
1307
+ ];
1308
+
1309
+ const sessions = {}; // token -> user
1310
+
1311
+ // Auth middleware
1312
+ const authMiddleware = (req, res, next) => {
1313
+ const token = req.headers['authorization']?.replace('Bearer ', '');
1314
+
1315
+ if (!token) {
1316
+ return res.error({
1317
+ code: 'NO_TOKEN_PROVIDED',
1318
+ message: 'Authorization token required'
1319
+ });
1320
+ }
1321
+
1322
+ const user = sessions[token];
1323
+
1324
+ if (!user) {
1325
+ return res.error({
1326
+ code: 'INVALID_TOKEN',
1327
+ message: 'Invalid or expired token'
1328
+ });
1329
+ }
1330
+
1331
+ req.user = user;
1332
+ next();
1333
+ };
1334
+
1335
+ // Role check middleware
1336
+ const requireRole = (...roles) => {
1337
+ return (req, res, next) => {
1338
+ if (!roles.includes(req.user.role)) {
1339
+ return res.error({
1340
+ code: 'FORBIDDEN',
1341
+ message: 'Insufficient permissions'
1342
+ });
1343
+ }
1344
+ next();
1345
+ };
1346
+ };
1347
+
1348
+ // Login route
1349
+ app.post('/auth/login', (req, res) => {
1350
+ const { username, password } = req.body;
1351
+
1352
+ const user = users.find(u =>
1353
+ u.username === username && u.password === password
1354
+ );
1355
+
1356
+ if (!user) {
1357
+ return res.error({
1358
+ code: 'INVALID_CREDENTIALS',
1359
+ status: 401,
1360
+ message: 'Invalid username or password'
1361
+ });
1362
+ }
1363
+
1364
+ // Generate token (in production, use JWT)
1365
+ const token = Math.random().toString(36).substring(7);
1366
+ sessions[token] = { id: user.id, username: user.username, role: user.role };
1367
+
1368
+ res.ok({
1369
+ token,
1370
+ user: { id: user.id, username: user.username, role: user.role }
1371
+ });
1372
+ });
1373
+
1374
+ // Protected route
1375
+ app.get('/api/profile', authMiddleware, (req, res) => {
1376
+ res.ok({ user: req.user });
1377
+ });
1378
+
1379
+ // Admin-only route
1380
+ app.get('/api/admin/stats', authMiddleware, requireRole('admin'), (req, res) => {
1381
+ res.ok({ totalUsers: users.length, sessions: Object.keys(sessions).length });
1382
+ });
1383
+
1384
+ // Logout
1385
+ app.post('/auth/logout', authMiddleware, (req, res) => {
1386
+ const token = req.headers['authorization']?.replace('Bearer ', '');
1387
+ delete sessions[token];
1388
+ res.ok({ message: 'Logged out successfully' });
1389
+ });
1390
+
1391
+ app.listen(3000);
1392
+ ```
1393
+
1394
+ ### File Upload Handler
1395
+
1396
+ ```javascript
1397
+ const Lieko = require('lieko-express');
1398
+ const { writeFileSync } = require('fs');
1399
+ const { join } = require('path');
1400
+
1401
+ const app = Lieko();
1402
+
1403
+ app.post('/upload', (req, res) => {
1404
+ if (!req.files || !req.files.file) {
1405
+ return res.error({
1406
+ code: 'INVALID_REQUEST',
1407
+ message: 'No file uploaded'
1408
+ });
1409
+ }
1410
+
1411
+ const file = req.files.file;
1412
+
1413
+ // Validate file type
1414
+ const allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
1415
+ if (!allowedTypes.includes(file.contentType)) {
1416
+ return res.error({
1417
+ code: 'INVALID_REQUEST',
1418
+ message: 'Only JPEG, PNG and GIF files are allowed'
1419
+ });
1420
+ }
1421
+
1422
+ // Validate file size (5MB max)
1423
+ const maxSize = 5 * 1024 * 1024;
1424
+ if (file.data.length > maxSize) {
1425
+ return res.error({
1426
+ code: 'INVALID_REQUEST',
1427
+ message: 'File size must not exceed 5MB'
1428
+ });
1429
+ }
1430
+
1431
+ // Save file
1432
+ const filename = `${Date.now()}-${file.filename}`;
1433
+ const filepath = join(__dirname, 'uploads', filename);
1434
+
1435
+ writeFileSync(filepath, file.data);
1436
+
1437
+ res.ok({
1438
+ filename,
1439
+ originalName: file.filename,
1440
+ size: file.data.length,
1441
+ contentType: file.contentType,
1442
+ url: `/uploads/${filename}`
1443
+ }, 'File uploaded successfully');
1444
+ });
1445
+
1446
+ app.listen(3000);
1447
+ ```
1448
+
1449
+ ### Rate Limiting Middleware
1450
+
1451
+ ```javascript
1452
+ const rateLimits = new Map();
1453
+
1454
+ const rateLimit = (options = {}) => {
1455
+ const {
1456
+ windowMs = 60000, // 1 minute
1457
+ maxRequests = 100
1458
+ } = options;
1459
+
1460
+ return (req, res, next) => {
1461
+ const key = req.ip;
1462
+ const now = Date.now();
1463
+
1464
+ if (!rateLimits.has(key)) {
1465
+ rateLimits.set(key, { count: 1, resetTime: now + windowMs });
1466
+ return next();
1467
+ }
1468
+
1469
+ const limit = rateLimits.get(key);
1470
+
1471
+ if (now > limit.resetTime) {
1472
+ limit.count = 1;
1473
+ limit.resetTime = now + windowMs;
1474
+ return next();
1475
+ }
1476
+
1477
+ if (limit.count >= maxRequests) {
1478
+ return res.error({
1479
+ code: 'TOO_MANY_REQUESTS',
1480
+ message: 'Rate limit exceeded. Please try again later.'
1481
+ });
1482
+ }
1483
+
1484
+ limit.count++;
1485
+ next();
1486
+ };
1487
+ };
1488
+
1489
+ // Usage
1490
+ app.use('/api', rateLimit({ windowMs: 60000, maxRequests: 100 }));
1491
+ ```
1492
+
1493
+ ### Request Logging Middleware
1494
+
1495
+ ```javascript
1496
+ const requestLogger = (req, res, next) => {
1497
+ const start = Date.now();
1498
+
1499
+ // Capture original end method
1500
+ const originalEnd = res.end;
1501
+
1502
+ res.end = function(...args) {
1503
+ const duration = Date.now() - start;
1504
+
1505
+ console.log({
1506
+ timestamp: new Date().toISOString(),
1507
+ method: req.method,
1508
+ url: req.url,
1509
+ status: res.statusCode,
1510
+ duration: `${duration}ms`,
1511
+ ip: req.ip,
1512
+ userAgent: req.headers['user-agent']
1513
+ });
1514
+
1515
+ originalEnd.apply(res, args);
1516
+ };
1517
+
1518
+ next();
1519
+ };
1520
+
1521
+ app.use(requestLogger);
1522
+ ```
1523
+
1524
+
1525
+ ## 🎯 Complete Application Example
1526
+
1527
+ See the [examples](./examples) directory for a full-featured application with:
1528
+ - User authentication
1529
+ - CRUD operations
1530
+ - Validation
1531
+ - File uploads
1532
+ - Nested routers
1533
+ - Middleware examples
1534
+ - Comprehensive test suite
1535
+
1536
+
1537
+ # 📊 Performance Tips (Suite)
1538
+
1539
+ 1. **Use async middlewares** for I/O operations
1540
+ 2. **Avoid heavy synchronous operations** inside request handlers
1541
+ 3. **Minimize deep-nested routers** unless needed
1542
+ 4. **Reuse validation schemas** instead of re-creating them for each request
1543
+ 5. **Use reverse proxy headers correctly** (`trust proxy`) when hosting behind Nginx
1544
+ 6. **Disable console logs in production** or use a real logger with adjustable log levels
1545
+
1546
+
1547
+ ## Debug & Introspection Tools
1548
+
1549
+ Lieko Express comes with powerful built-in development and debugging utilities.
1550
+
1551
+ ### 🐞 Enable Debug Mode
1552
+
1553
+ ```js
1554
+ const app = Lieko();
1555
+
1556
+ // Enable debug mode (never use in production!)
1557
+ app.settings.debug = true;
1558
+ // or simply
1559
+ app.enable('debug')
1560
+ ```
1561
+
1562
+ When `debug` is enabled, Lieko automatically logs every request with a beautiful, color-coded output containing:
1563
+
1564
+ - Method + full URL
1565
+ - Real client IP (IPv4/IPv6)
1566
+ - Status code (color-coded)
1567
+ - Ultra-precise response time (µs / ms / s)
1568
+ - Route parameters, query string, body, uploaded files
1569
+
1570
+ **Example output:**
1571
+ ```
1572
+ DEBUG REQUEST
1573
+ → GET /api/users/42?page=2&active=true
1574
+ → IP: 127.0.0.1
1575
+ → Status: 200
1576
+ → Duration: 1.847ms
1577
+ → Params: {"id":"42"}
1578
+ → Query: {"page":2,"active":true}
1579
+ → Body: {}
1580
+ → Files:
1581
+ ---------------------------------------------
1582
+ ```
1583
+
1584
+ **Warning:** Never enable `app.enable('debug')` in production (performance impact + potential sensitive data leak).
1585
+
1586
+ ### List All Registered Routes
1587
+
1588
+ ```js
1589
+ // Returns an array of route objects — perfect for tests or auto-docs
1590
+ const routes = app.listRoutes();
1591
+ console.log(routes);
1592
+ /*
1593
+ [
1594
+ { method: 'GET', path: '/api/users', middlewares: 1 },
1595
+ { method: 'POST', path: '/api/users', middlewares: 2 },
1596
+ { method: 'GET', path: '/api/users/:id', middlewares: 0 },
1597
+ { method: 'DELETE', path: '/api/users/:id', middlewares: 1 },
1598
+ ...
1599
+ ]
1600
+ */
1601
+ ```
1602
+
1603
+ Ideal for generating OpenAPI specs, runtime validation, or integration tests.
1604
+
1605
+ ### Pretty-Print All Routes in Console
1606
+
1607
+ ```js
1608
+ // Display a clean table
1609
+ app.printRoutes();
1610
+ ```
1611
+
1612
+ **Sample output:**
1613
+ ```
1614
+ Registered Routes:
1615
+
1616
+ GET /api/users (middlewares: 1)
1617
+ POST /api/users (middlewares: 2)
1618
+ GET /api/users/:id (middlewares: 0)
1619
+ DELETE /api/users/:id (middlewares: 1)
1620
+ PATCH /api/users/:id (middlewares: 0)
1621
+ ALL /webhook/* (middlewares: 0)
1622
+
1623
+ ```
1624
+
1625
+ ### Nested (group-based):
1626
+
1627
+ ```js
1628
+ app.printRoutesNested();
1629
+ ```
1630
+
1631
+ Example:
1632
+
1633
+ ```
1634
+ /api [auth]
1635
+ /api/admin [auth, requireAdmin]
1636
+ GET /users
1637
+ POST /users
1638
+ GET /profile
1639
+ ```
1640
+
1641
+ Perfect to quickly verify that all your routes and middlewares are correctly registered.
1642
+
1643
+ ## ⚙️ Application Settings
1644
+
1645
+ Lieko Express provides a small but effective settings mechanism.
1646
+
1647
+ You can configure:
1648
+
1649
+ ```js
1650
+ app.set('trust proxy', value);
1651
+ app.set('debug', boolean);
1652
+ app.set('x-powered-by', false);
1653
+ app.set('strictTrailingSlash', false);
1654
+ app.set('allowTrailingSlash', true);
1655
+ ```
1656
+
1657
+ # 🌐 Trust Proxy & IP Parsing
1658
+
1659
+ Lieko improves on Express with a powerful trust proxy system.
1660
+
1661
+ ### Configure:
1662
+
1663
+ ```js
1664
+ app.set('trust proxy', true);
1665
+ ```
1666
+
1667
+ ### Supported values:
1668
+
1669
+ * `true` — trust all
1670
+ * `"loopback"`
1671
+ * `"127.0.0.1"`
1672
+ * `["10.0.0.1", "10.0.0.2"]`
1673
+ * custom function `(ip) => boolean`
1674
+
1675
+ ### Provided fields:
1676
+
1677
+ ```js
1678
+ req.ip.raw
1679
+ req.ip.ipv4
1680
+ req.ip.ipv6
1681
+ req.ips // full proxy chain
1682
+ ```
1683
+
1684
+ Lieko correctly handles:
1685
+
1686
+ * IPv6
1687
+ * IPv4-mapped IPv6 (`::ffff:127.0.0.1`)
1688
+ * Multi-proxy headers
1689
+
1690
+
1691
+ ## 🧩 Internals & Architecture
1692
+
1693
+ This section describes how Lieko Express works under the hood.
1694
+
1695
+ ### Request Lifecycle
1696
+
1697
+ 1. Enhance request object (IP parsing, protocol, host…)
1698
+ 2. Parse query string
1699
+ 3. Parse body (`json`, `urlencoded`, `multipart/form-data`, or text)
1700
+ 4. Apply **global middlewares**
1701
+ 5. Match route using regex-based router
1702
+ 6. Apply **route middlewares**
1703
+ 7. Execute route handler
1704
+ 8. Apply 404 handler if no route matched
1705
+ 9. Send error responses using the built-in error system
1706
+
1707
+ ### Router Internals
1708
+
1709
+ Routes are converted to regular expressions:
1710
+
1711
+ ```txt
1712
+ /users/:id → ^/users/(?<id>[^/]+)$
1713
+ /files/* → ^/files/.*$
1714
+ ```
1715
+
1716
+ This allows:
1717
+
1718
+ * Named parameters
1719
+ * Wildcards
1720
+ * Fast matching
1721
+
1722
+
1723
+ ## 🧱 Extending Lieko Express
1724
+
1725
+ Because the framework is intentionally small, you can easily extend it.
1726
+
1727
+ ### Custom Response Helpers
1728
+
1729
+ ```js
1730
+ app.use((req, res, next) => {
1731
+ res.created = (data) => {
1732
+ res.status(201).json({ success: true, data });
1733
+ };
1734
+ next();
1735
+ });
1736
+ ```
1737
+
1738
+ ### Custom Middlewares
1739
+
1740
+ ```js
1741
+ const timing = (req, res, next) => {
1742
+ const start = Date.now();
1743
+ res.end = ((original) => (...args) => {
1744
+ console.log(`⏱️ ${req.method} ${req.url} took ${Date.now() - start}ms`);
1745
+ original(...args);
1746
+ })(res.end);
1747
+ next();
1748
+ };
1749
+
1750
+ app.use(timing);
1751
+ ```
1752
+
1753
+ # 🔌 Plugins
1754
+
1755
+ A plugin is simply a function receiving `app`:
1756
+
1757
+ ```js
1758
+ function myPlugin(app) {
1759
+ app.get('/plugin-test', (req, res) => res.ok("OK"));
1760
+ }
1761
+
1762
+ myPlugin(app);
1763
+ ```
1764
+
1765
+ ## 🚀 Deploying Lieko Express
1766
+
1767
+ ### Node.js (PM2)
1768
+
1769
+ ```bash
1770
+ pm2 start app.js
1771
+ ```
1772
+
1773
+ ## 📦 API Reference
1774
+
1775
+ ### `Lieko()`
1776
+
1777
+ Creates a new application instance.
1778
+
1779
+ ### `Lieko.Router()`
1780
+
1781
+ Creates a modular router (internally: a Lieko app).
1782
+
1783
+ ### `app.use(...)`
1784
+
1785
+ Supported patterns:
1786
+
1787
+ | Signature | Description |
1788
+ | --------------------------------- | ------------------------ |
1789
+ | `app.use(fn)` | Global middleware |
1790
+ | `app.use(path, fn)` | Path-filtered middleware |
1791
+ | `app.use(path, router)` | Mount router |
1792
+ | `app.use(path, mw1, mw2, router)` | Mix middlewares + router |
1793
+
1794
+ ### `app.get/post/put/delete/patch/all(path, ...handlers)`
1795
+
1796
+ Registers routes with optional middlewares.
1797
+
1798
+ ### `app.notFound(handler)`
1799
+
1800
+ Custom 404 callback.
1801
+
1802
+ ```js
1803
+ app.notFound((req, res) => {
1804
+ res.error({
1805
+ code: "NOT_FOUND",
1806
+ message: `Route "${req.method} ${req.originalUrl}" does not exist`
1807
+ });
1808
+ });
1809
+
1810
+ // With logging
1811
+ app.notFound((req, res) => {
1812
+ console.warn(`404: ${req.method} ${req.originalUrl}`);
1813
+ res.error("ROUTE_NOT_FOUND");
1814
+ });
1815
+
1816
+ // With HTML Response
1817
+ app.notFound((req, res) => {
1818
+ res.status(404).send("<h1>Page Not Found</h1>");
1819
+ });
1820
+ ```
1821
+
1822
+ ### `app.errorHandler(handler)`
1823
+
1824
+ Register a custom 500 handler.
1825
+
1826
+
1827
+
1828
+ ## 🔍 Known Limitations
1829
+
1830
+ Because Lieko Express is minimalistic:
1831
+
1832
+ * No template engine
1833
+ * No streaming uploads for multipart/form-data (parsed in memory)
1834
+ * No built-in cookies/sessions
1835
+ * No WebSocket support yet
1836
+ * Routers cannot have their own `notFound` handler (inherited from parent)
1837
+
1838
+ Future versions may address some of these.
1839
+
1840
+
1841
+ ## 🤝 Contributing
1842
+
1843
+ Contributions are welcome!
1844
+
1845
+ 1. Fork the repository
1846
+ 2. Create a feature branch
1847
+ 3. Commit your changes
1848
+ 4. Open a pull request
1849
+
1850
+
1851
+
1852
+ ## 📄 License
1853
+
1854
+ MIT License — free to use in personal and commercial projects.