lieko-express 0.0.1 β†’ 0.0.2

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