lieko-express 0.0.20 → 1.0.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 eiwSrvt
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1 +1,387 @@
1
- doc to finish
1
+ # Lieko Express
2
+
3
+ A modern, minimal, Express-like framework for Node.js with built-in body parsing, CORS, validation, and more. Lieko-express is designed to be a drop-in replacement for Express.js with additional features and better performance.
4
+
5
+ Online documentation
6
+ [Lieko Express Documentation](https://express.lieko.app/)
7
+
8
+ ## Key Features
9
+
10
+ - **Express-compatible API**: Familiar routing, middleware, and request/response handling.
11
+ - **Built-in Body Parsing**: JSON, URL-encoded, and multipart form-data support.
12
+ - **CORS Support**: Configurable cross-origin resource sharing with flexible options.
13
+ - **Schema Validation**: Built-in validation system with comprehensive validators.
14
+ - **Static File Serving**: Efficient static file middleware with caching and ETags.
15
+ - **Template Engine**: Simple HTML templating with support for custom engines.
16
+ - **Route Groups**: Organize routes with nested groups and shared middleware.
17
+ - **Debug Mode**: Detailed request logging with timing and payload information.
18
+
19
+ > **Note**: Lieko-express requires Node.js ≥14.0.0.
20
+
21
+ ## Installation
22
+
23
+ ### NPM
24
+ ```bash
25
+ npm install lieko-express
26
+ ```
27
+
28
+ ### Yarn
29
+ ```bash
30
+ yarn add lieko-express
31
+ ```
32
+
33
+ ## Quick Start
34
+
35
+ Create your first Lieko-express application:
36
+
37
+ ```javascript
38
+ const Lieko = require('lieko-express');
39
+ const app = Lieko();
40
+
41
+ app.get('/', (req, res) => {
42
+ res.json({ message: 'Hello World!' });
43
+ });
44
+
45
+ app.listen(3000, () => {
46
+ console.log('Server running on http://localhost:3000');
47
+ });
48
+ ```
49
+
50
+ ### Basic REST API Example
51
+
52
+ ```javascript
53
+ const Lieko = require('lieko-express');
54
+ const app = Lieko();
55
+
56
+ // Enable debug mode
57
+ app.debug(true);
58
+
59
+ // Simple in-memory database
60
+ const users = [];
61
+ let idCounter = 1;
62
+
63
+ // Get all users
64
+ app.get('/api/users', (req, res) => {
65
+ res.json(users);
66
+ });
67
+
68
+ // Get user by ID
69
+ app.get('/api/users/:id', (req, res) => {
70
+ const user = users.find(u => u.id === req.params.id);
71
+
72
+ if (!user) {
73
+ return res.status(404).json({
74
+ error: 'User not found'
75
+ });
76
+ }
77
+
78
+ res.json(user);
79
+ });
80
+
81
+ // Create user
82
+ app.post('/api/users', (req, res) => {
83
+ const user = {
84
+ id: String(idCounter++),
85
+ name: req.body.name,
86
+ email: req.body.email,
87
+ createdAt: new Date().toISOString()
88
+ };
89
+
90
+ users.push(user);
91
+ res.status(201).json(user);
92
+ });
93
+
94
+ // Update user
95
+ app.patch('/api/users/:id', (req, res) => {
96
+ const user = users.find(u => u.id === req.params.id);
97
+
98
+ if (!user) {
99
+ return res.status(404).json({
100
+ error: 'User not found'
101
+ });
102
+ }
103
+
104
+ Object.assign(user, req.body);
105
+ res.json(user);
106
+ });
107
+
108
+ // Delete user
109
+ app.delete('/api/users/:id', (req, res) => {
110
+ const index = users.findIndex(u => u.id === req.params.id);
111
+
112
+ if (index === -1) {
113
+ return res.status(404).json({
114
+ error: 'User not found'
115
+ });
116
+ }
117
+
118
+ users.splice(index, 1);
119
+ res.status(204).end();
120
+ });
121
+
122
+ app.listen(3000, () => {
123
+ console.log('API running on http://localhost:3000');
124
+ });
125
+ ```
126
+
127
+ ## Configuration
128
+
129
+ Customize Lieko-express with settings:
130
+
131
+ ```javascript
132
+ const app = Lieko();
133
+
134
+ // Enable/disable features
135
+ app.set('x-powered-by', 'MyApp');
136
+ app.set('trust proxy', true);
137
+ app.set('views', './templates');
138
+ app.set('view engine', 'html');
139
+
140
+ // Enable debug mode
141
+ app.debug(true);
142
+
143
+ // Disable strict trailing slash
144
+ app.set('strictTrailingSlash', false);
145
+ app.set('allowTrailingSlash', true);
146
+ ```
147
+
148
+ ### Configuration Options
149
+
150
+ | Setting | Type | Default | Description |
151
+ |----------------------|---------------|---------------|---------------------------------|
152
+ | `debug` | boolean | false | Enable detailed request logging |
153
+ | `x-powered-by` | string\|boolean | 'lieko-express' | X-Powered-By header value |
154
+ | `trust proxy` | boolean | false | Trust X-Forwarded-* headers |
155
+ | `strictTrailingSlash` | boolean | true | Strict trailing slash matching |
156
+ | `allowTrailingSlash` | boolean | true | Allow optional trailing slash |
157
+ | `views` | string | './views' | Template directory path |
158
+ | `view engine` | string | 'html' | Default template engine |
159
+
160
+ ## Routing
161
+
162
+ Define routes with flexible patterns:
163
+
164
+ ```javascript
165
+ // GET request
166
+ app.get('/users', (req, res) => {
167
+ res.json({ users: [] });
168
+ });
169
+
170
+ // POST request
171
+ app.post('/users', (req, res) => {
172
+ res.status(201).json({ message: 'User created' });
173
+ });
174
+
175
+ // Route parameters
176
+ app.get('/users/:id', (req, res) => {
177
+ const userId = req.params.id;
178
+ res.json({ userId });
179
+ });
180
+
181
+ // Multiple handlers
182
+ app.get('/protected', authMiddleware, (req, res) => {
183
+ res.json({ message: 'Protected route' });
184
+ });
185
+ ```
186
+
187
+ ## Middleware
188
+
189
+ Middleware for request processing:
190
+
191
+ ```javascript
192
+ // Application-level
193
+ app.use((req, res, next) => {
194
+ console.log(`${req.method} ${req.url}`);
195
+ next();
196
+ });
197
+
198
+ // Error handling
199
+ app.errorHandler((err, req, res, next) => {
200
+ console.error('Error:', err.message);
201
+ res.status(err.status || 500).json({
202
+ error: {
203
+ message: err.message,
204
+ code: err.code || 'SERVER_ERROR'
205
+ }
206
+ });
207
+ });
208
+ ```
209
+
210
+ ## Request Object
211
+
212
+ Properties and methods:
213
+
214
+ | Property | Type | Description |
215
+ |-------------------|---------|--------------------------------------|
216
+ | `req.body` | object | Parsed request body |
217
+ | `req.params` | object | Route parameters |
218
+ | `req.query` | object | Query string parameters |
219
+ | `req.files` | object | Uploaded files |
220
+ | `req.headers` | object | HTTP headers |
221
+ | `req.method` | string | HTTP method |
222
+
223
+ Methods like `req.get('Content-Type')`, `req.accepts(['json', 'html'])`.
224
+
225
+ ## Response Object
226
+
227
+ Methods for sending responses:
228
+
229
+ ```javascript
230
+ // Send JSON
231
+ res.json({ message: 'Hello' });
232
+
233
+ // Status and helpers
234
+ res.status(201).json({ message: 'Created' });
235
+ res.ok({ data: users });
236
+ res.created({ user });
237
+ res.noContent();
238
+ res.badRequest('Invalid input');
239
+ res.redirect('/new-url');
240
+ ```
241
+
242
+ ## Body Parsing
243
+
244
+ Automatic parsing with limits:
245
+
246
+ ```javascript
247
+ app.bodyParser({
248
+ limit: '50mb',
249
+ extended: true,
250
+ strict: true
251
+ });
252
+ ```
253
+
254
+ ## CORS
255
+
256
+ Configure CORS:
257
+
258
+ ```javascript
259
+ app.cors({
260
+ origin: '*',
261
+ methods: ['GET', 'POST'],
262
+ credentials: true
263
+ });
264
+ ```
265
+
266
+ ## Static Files
267
+
268
+ Serve static files:
269
+
270
+ ```javascript
271
+ app.use(app.static('public'));
272
+ ```
273
+
274
+ ## Template Engine
275
+
276
+ Render templates:
277
+
278
+ ```javascript
279
+ res.render('index', {
280
+ title: 'My Page',
281
+ user: { name: 'Alice' }
282
+ });
283
+ ```
284
+
285
+ ## Schema Validation
286
+
287
+ Validate requests:
288
+
289
+ ```javascript
290
+ const { Schema, validators: v, validate } = require('lieko-express');
291
+
292
+ const schema = new Schema({
293
+ name: [v.required(), v.string(), v.minLength(3)],
294
+ age: [v.optional(), v.number(), v.min(18)]
295
+ });
296
+
297
+ app.post('/users', validate(schema), (req, res) => {
298
+ res.created(req.body);
299
+ });
300
+ ```
301
+
302
+ ## Route Groups
303
+
304
+ Organize routes:
305
+
306
+ ```javascript
307
+ app.group('/api', (api) => {
308
+ api.get('/users', handler);
309
+ api.post('/users', handler);
310
+ });
311
+ ```
312
+
313
+ ## Error Handling
314
+
315
+ Custom handlers:
316
+
317
+ ```javascript
318
+ app.notFound((req, res) => {
319
+ res.notFound('Page not found');
320
+ });
321
+ ```
322
+
323
+ ## Cookies
324
+
325
+ Set and clear cookies:
326
+
327
+ ```javascript
328
+ res.cookie('session', 'abc123', {
329
+ maxAge: 86400000,
330
+ httpOnly: true,
331
+ secure: true
332
+ });
333
+
334
+ res.clearCookie('session');
335
+ ```
336
+
337
+ ## File Uploads
338
+
339
+ Handle uploads:
340
+
341
+ ```javascript
342
+ app.post('/upload', (req, res) => {
343
+ const file = req.files.avatar;
344
+ // Process file
345
+ });
346
+ ```
347
+
348
+ ## API Methods Reference
349
+
350
+ See the full documentation for complete lists of application and response methods.
351
+
352
+ ## Validators Reference
353
+
354
+ Built-in validators like `v.required()`, `v.email()`, `v.min(10)`.
355
+
356
+ ## Best Practices
357
+
358
+ - Use modular routes and controllers.
359
+ - Handle environment variables with `dotenv`.
360
+ - Implement security headers.
361
+ - Add rate limiting for APIs.
362
+
363
+ ## Examples
364
+
365
+ ### Basic REST API
366
+
367
+ (See code in Quick Start)
368
+
369
+ ### Authentication
370
+
371
+ JWT-based auth example (see full code in documentation).
372
+
373
+ ### File Upload
374
+
375
+ Upload service example (see full code in documentation).
376
+
377
+ ### Real-world App
378
+
379
+ Blog API with auth and posts (see full code in documentation).
380
+
381
+ ## Contributing
382
+
383
+ Contributions welcome! Please submit issues or pull requests on GitHub.
384
+
385
+ ## License
386
+
387
+ MIT License. See [LICENSE](LICENSE) for details.
package/lib/cors.js CHANGED
@@ -56,7 +56,6 @@ module.exports = function cors(userOptions = {}) {
56
56
  if (!allowed) {
57
57
  res.statusCode = 403;
58
58
  return res.end(JSON.stringify({
59
- success: false,
60
59
  error: "Origin Forbidden",
61
60
  message: `Origin "${requestOrigin}" is not allowed`
62
61
  }));
package/lib/schema.js CHANGED
@@ -371,15 +371,11 @@ function validate(schema) {
371
371
  try {
372
372
  schema.validate(req.body);
373
373
  next();
374
- } catch (error) {
375
- if (error instanceof ValidationError) {
376
- return res.status(400).json({
377
- success: false,
378
- message: 'Validation failed',
379
- errors: error.errors
380
- });
374
+ } catch (err) {
375
+ if (err instanceof ValidationError) {
376
+ return res.status(400).json({ error: { status: 400, type: "VALIDATION_ERROR", message: 'Validation failed', details: err.errors } });
381
377
  }
382
- throw error;
378
+ throw err;
383
379
  }
384
380
  };
385
381
  }
package/lieko-express.js CHANGED
@@ -781,7 +781,8 @@ ${cyan} (req, res, next) => {
781
781
  return res.status(500).json({
782
782
  error: {
783
783
  message: "Internal Server Error",
784
- code: 500
784
+ status: 500,
785
+ type: "InternalServerError"
785
786
  }
786
787
  });
787
788
  }
@@ -812,7 +813,7 @@ ${cyan} (req, res, next) => {
812
813
  res.status(500).json({
813
814
  error: {
814
815
  message: "Internal Server Error",
815
- code: 500
816
+ status: 500
816
817
  }
817
818
  });
818
819
  }
@@ -827,13 +828,12 @@ ${cyan} (req, res, next) => {
827
828
  return res.status(500).json({
828
829
  error: {
829
830
  message: "Invalid error format passed to res.error()",
830
- code: 500
831
+ status: 500
831
832
  }
832
833
  });
833
834
  }
834
835
 
835
836
  const HTTP_STATUS = {
836
- // 4xx – CLIENT ERRORS
837
837
  INVALID_REQUEST: 400,
838
838
  VALIDATION_FAILED: 400,
839
839
  NO_TOKEN_PROVIDED: 401,
@@ -844,18 +844,21 @@ ${cyan} (req, res, next) => {
844
844
  CONFLICT: 409,
845
845
  RECORD_EXISTS: 409,
846
846
  TOO_MANY_REQUESTS: 429,
847
-
848
- // 5xx – SERVER ERRORS
849
847
  SERVER_ERROR: 500,
850
848
  SERVICE_UNAVAILABLE: 503
851
849
  };
852
850
 
853
- const status = errorObj.status || HTTP_STATUS[errorObj.code] || 500;
851
+ let currentStatus = res.statusCode || 200;
852
+ let desiredStatus = errorObj.status || HTTP_STATUS[errorObj.status];
853
+
854
+ const finalStatus = (currentStatus >= 400 && currentStatus < 600)
855
+ ? currentStatus
856
+ : (desiredStatus || 500);
854
857
 
855
- return res.status(status).json({
858
+ return res.status(finalStatus).json({
856
859
  error: {
857
860
  message: errorObj.message || 'An error occurred',
858
- code: errorObj.code || 500,
861
+ status: errorObj.status || finalStatus,
859
862
  ...errorObj
860
863
  }
861
864
  });
@@ -982,7 +985,7 @@ ${cyan} (req, res, next) => {
982
985
  return res.status(413).json({
983
986
  error: {
984
987
  message: 'Payload Too Large',
985
- code: 413
988
+ status: 413
986
989
  }
987
990
  });
988
991
  }
@@ -1032,7 +1035,7 @@ ${cyan} (req, res, next) => {
1032
1035
 
1033
1036
  if (!route) {
1034
1037
  if (this.notFoundHandler) return this.notFoundHandler(req, res);
1035
- return res.status(404).json({ error: { message: 'Route not found', code: 404 } });
1038
+ return res.status(404).error('Not Found');
1036
1039
  }
1037
1040
 
1038
1041
  req.params = route.params;
@@ -1058,7 +1061,11 @@ ${cyan} (req, res, next) => {
1058
1061
 
1059
1062
  if (res.headersSent) return;
1060
1063
 
1061
- await route.handler(req, res);
1064
+ await route.handler(req, res, (err) => {
1065
+ if (err) {
1066
+ return this._runErrorHandlers(err, req, res);
1067
+ }
1068
+ });
1062
1069
 
1063
1070
  } catch (error) {
1064
1071
  if (!res.headersSent) {
@@ -1138,16 +1145,21 @@ ${cyan} (req, res, next) => {
1138
1145
  .map(item => item.type);
1139
1146
  };
1140
1147
 
1141
- const accepts = (types) => {
1148
+ const acceptedTypes = parseAccept(req.headers['accept']);
1149
+
1150
+ req.accepts = function (types) {
1151
+ if (arguments.length === 0) {
1152
+ return acceptedTypes;
1153
+ }
1154
+
1142
1155
  if (!Array.isArray(types)) types = [types];
1143
- const accepted = parseAccept(req.headers['accept']);
1144
1156
 
1145
1157
  for (const type of types) {
1146
1158
  const t = type.toLowerCase();
1147
1159
 
1148
- if (accepted.includes(t)) return type;
1160
+ if (acceptedTypes.includes(t)) return type;
1149
1161
 
1150
- if (accepted.some(a => {
1162
+ if (acceptedTypes.some(a => {
1151
1163
  if (a === '*/*') return true;
1152
1164
  if (a.endsWith('/*')) {
1153
1165
  const prefix = a.slice(0, -1);
@@ -1162,8 +1174,24 @@ ${cyan} (req, res, next) => {
1162
1174
  return false;
1163
1175
  };
1164
1176
 
1165
- req.accepts = function (types) {
1166
- return accepts(types);
1177
+ req.responseType = function () {
1178
+ const accepted = this.accepts();
1179
+
1180
+ if (accepted.length > 0) {
1181
+ for (const type of accepted) {
1182
+ if (type === 'text/html') return 'html';
1183
+ if (type === 'application/json') return 'json';
1184
+ if (type === '*/*') break;
1185
+ }
1186
+ }
1187
+
1188
+ const ua = (this.headers['user-agent'] || '').toLowerCase();
1189
+ const isApi = ua.includes('curl') ||
1190
+ ua.includes('postman') ||
1191
+ this.xhr ||
1192
+ this.headers['content-type']?.includes('application/json');
1193
+
1194
+ return isApi ? 'json' : 'html';
1167
1195
  };
1168
1196
 
1169
1197
  req.acceptsLanguages = function (langs) {
@@ -1538,6 +1566,7 @@ ${cyan} (req, res, next) => {
1538
1566
  status = 404;
1539
1567
  message = 'File Not Found';
1540
1568
  details = `The file "${filePath}" does not exist.\nFull path tried: ${file}`;
1569
+ console.error(details, err);
1541
1570
  } else if (err.code === 'FORBIDDEN') {
1542
1571
  status = 403;
1543
1572
  message = 'Forbidden';
@@ -1576,11 +1605,9 @@ ${cyan} (req, res, next) => {
1576
1605
  if (message !== undefined) payload.message = message;
1577
1606
  return res.json(payload);
1578
1607
  };
1579
- //res.success = res.ok;
1580
1608
 
1581
1609
  res.created = (data, message = 'Resource created successfully') => {
1582
- const payload = { success: true, data, message };
1583
- return res.status(201).json(payload);
1610
+ return res.status(201).json({ data, message });
1584
1611
  };
1585
1612
 
1586
1613
  res.noContent = () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lieko-express",
3
- "version": "0.0.20",
3
+ "version": "1.0.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/eiwSrvt/lieko-express"