kempo-server 1.4.3 → 1.4.6

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 (47) hide show
  1. package/.github/copilot-instructions.md +96 -96
  2. package/.github/workflows/publish-npm.yml +45 -0
  3. package/README.md +650 -650
  4. package/builtinMiddleware.js +136 -136
  5. package/defaultConfig.js +129 -129
  6. package/docs/.config.json +5 -5
  7. package/docs/.config.json.example +19 -19
  8. package/docs/api/user/[id]/GET.js +15 -15
  9. package/docs/api/user/[id]/[info]/DELETE.js +12 -12
  10. package/docs/api/user/[id]/[info]/GET.js +17 -17
  11. package/docs/api/user/[id]/[info]/POST.js +18 -18
  12. package/docs/api/user/[id]/[info]/PUT.js +19 -19
  13. package/docs/configuration.html +119 -119
  14. package/docs/examples.html +201 -201
  15. package/docs/getting-started.html +72 -72
  16. package/docs/index.html +81 -81
  17. package/docs/manifest.json +87 -87
  18. package/docs/middleware.html +147 -147
  19. package/docs/request-response.html +95 -95
  20. package/docs/routing.html +77 -77
  21. package/example-middleware.js +23 -23
  22. package/example.config.json +50 -50
  23. package/findFile.js +138 -138
  24. package/getFiles.js +72 -72
  25. package/getFlags.js +34 -34
  26. package/index.js +47 -47
  27. package/middlewareRunner.js +25 -25
  28. package/package.json +10 -6
  29. package/requestWrapper.js +87 -87
  30. package/responseWrapper.js +204 -204
  31. package/router.js +285 -285
  32. package/serveFile.js +71 -71
  33. package/tests/builtinMiddleware-cors.node-test.js +17 -17
  34. package/tests/builtinMiddleware.node-test.js +74 -74
  35. package/tests/defaultConfig.node-test.js +13 -13
  36. package/tests/example-middleware.node-test.js +31 -31
  37. package/tests/findFile.node-test.js +46 -46
  38. package/tests/getFiles.node-test.js +25 -25
  39. package/tests/getFlags.node-test.js +30 -30
  40. package/tests/index.node-test.js +23 -23
  41. package/tests/middlewareRunner.node-test.js +18 -18
  42. package/tests/requestWrapper.node-test.js +51 -51
  43. package/tests/responseWrapper.node-test.js +74 -74
  44. package/tests/router-middleware.node-test.js +46 -46
  45. package/tests/router.node-test.js +88 -88
  46. package/tests/serveFile.node-test.js +52 -52
  47. package/tests/test-utils.js +106 -106
package/README.md CHANGED
@@ -1,650 +1,650 @@
1
- # Kempo Server
2
-
3
- A lightweight, zero-dependency, file based routing server.
4
-
5
- ## Getting Started
6
-
7
- 1. Install the npm package.
8
-
9
- ```bash
10
- npm install kempo-server
11
- ```
12
-
13
- 2. Add it to your `package.json` scripts, use the `--root` flag to tell it where the root of your site is.
14
-
15
- ```
16
- {
17
- ...
18
- "scripts": {
19
- "start": "kempo-server --root public"
20
- }
21
- ...
22
- }
23
- ```
24
-
25
- 3. Run it in your terminal.
26
-
27
- ```bash
28
- npm run start
29
- ```
30
-
31
- ## Routes
32
-
33
- A route is a request to a directory that will be handled by a file. To define routes, create the directory structure to the route and create a file with the name of the method that this file will handle. For example `GET.js` will handle the `GET` requests, `POST.js` will handle the `POST` requests and so on. Use `index.js` to handle all request types.
34
-
35
- The Javascript file must have a **default** export that is the function that will handle the request. It will be passed a `request`, `response`, and `params` arguments (See [Dynamic Routes](#dynamic-routes) below).
36
-
37
- For example this directory structure:
38
-
39
- ```
40
- my/
41
- ├─ route/
42
- │ ├─ GET.js
43
- │ ├─ POST.js
44
- ├─ other/
45
- │ ├─ route/
46
- │ │ ├─ GET.js
47
- ```
48
-
49
- Would be used to handle `GET my/route/`, `POST my/route/` and `GET my/other/route/`
50
-
51
- ### HTML Routes
52
-
53
- Just like JS files, HTML files can be used to define a route. Use `GET.html`, `POST.html`, etc... to define files that will be served when that route is requested.
54
-
55
- ```
56
- my/
57
- ├─ route/
58
- │ ├─ GET.js
59
- │ ├─ POST.js
60
- ├─ other/
61
- │ ├─ route/
62
- │ │ ├─ GET.js
63
- │ ├─ POST.html
64
- │ ├─ GET.html
65
- ```
66
-
67
- ### `index` fallbacks
68
-
69
- `index.js` or `index.html` will be used as a fallback for all routes if a *method* file is not defined. In the above examples we do not have any routes defined for `DELETE`, `PUT` `PATCH`, etc... so lets use an `index.js` and `index.html` to be a "catch-all" for all the methods we have not created handlers for.
70
-
71
- ```
72
- my/
73
- ├─ route/
74
- │ ├─ GET.js
75
- │ ├─ POST.js
76
- │ ├─ index.js
77
- ├─ other/
78
- │ ├─ route/
79
- │ │ ├─ GET.js
80
- │ │ ├─ index.js
81
- │ ├─ POST.html
82
- │ ├─ GET.html
83
- │ ├─ index.html
84
- ├─ index.html
85
- ```
86
-
87
- ## Dynamic Routes
88
-
89
- A dynamic route is a route with a "param" in its path. To define the dynamic parts of the route just wrap the directory name in square brackets. For example if you wanted to get a users profile, or perform CRUD operations on a user you might create the following directory structure.
90
-
91
- ```
92
- api/
93
- ├─ user/
94
- │ ├─ [id]/
95
- │ │ ├─ [info]/
96
- │ │ │ ├─ GET.js
97
- │ │ │ ├─ DELETE.js
98
- │ │ │ ├─ PUT.js
99
- │ │ │ ├─ POST.js
100
- │ │ ├─ GET.js
101
- ```
102
-
103
- When a request is made to `/api/user/123/info`, the route file `api/user/[id]/[info]/GET.js` would be executed and receive a request object with `request.params` containing `{ id: "123", info: "info" }`.
104
-
105
- ## Request Object
106
-
107
- Kempo Server provides a request object that makes working with HTTP requests easier:
108
-
109
- ### Properties
110
- - `request.params` - Route parameters from dynamic routes (e.g., `{ id: "123", info: "info" }`)
111
- - `request.query` - Query string parameters as an object (e.g., `{ page: "1", limit: "10" }`)
112
- - `request.path` - The pathname of the request URL
113
- - `request.method` - HTTP method (GET, POST, etc.)
114
- - `request.headers` - Request headers object
115
- - `request.url` - Full request URL
116
-
117
- ### Methods
118
- - `await request.json()` - Parse request body as JSON
119
- - `await request.text()` - Get request body as text
120
- - `await request.body()` - Get raw request body as string
121
- - `await request.buffer()` - Get request body as Buffer
122
- - `request.get(headerName)` - Get specific header value
123
- - `request.is(type)` - Check if content-type contains specified type
124
-
125
- ### Example Route File
126
-
127
- Here's an example of what a route file might look like:
128
-
129
- ```javascript
130
- // api/user/[id]/GET.js
131
- export default async function(request, response) {
132
- const { id } = request.params;
133
- const { includeDetails } = request.query;
134
-
135
- // Fetch user data from database
136
- const userData = await getUserById(id);
137
-
138
- if (!userData) {
139
- return response.status(404).json({ error: 'User not found' });
140
- }
141
-
142
- response.json(userData);
143
- }
144
- ```
145
-
146
- ### POST Request Example
147
-
148
- ```javascript
149
- // api/user/[id]/POST.js
150
- export default async function(request, response) {
151
- const { id } = request.params;
152
-
153
- try {
154
- const updateData = await request.json();
155
-
156
- // Update user in database
157
- const updatedUser = await updateUser(id, updateData);
158
-
159
- response.json(updatedUser);
160
- } catch (error) {
161
- response.status(400).json({ error: 'Invalid JSON' });
162
- }
163
- }
164
- ```
165
-
166
- ## Response Object
167
-
168
- Kempo Server also provides a response object that makes sending responses easier:
169
-
170
- ### Methods
171
- - `response.json(object)` - Send JSON response with automatic content-type
172
- - `response.send(data)` - Send response (auto-detects content type)
173
- - `response.html(htmlString)` - Send HTML response
174
- - `response.text(textString)` - Send plain text response
175
- - `response.status(code)` - Set status code (chainable)
176
- - `response.set(field, value)` - Set header (chainable)
177
- - `response.type(contentType)` - Set content type (chainable)
178
- - `response.redirect(url, statusCode)` - Redirect to URL
179
- - `response.cookie(name, value, options)` - Set cookie
180
- - `response.clearCookie(name, options)` - Clear cookie
181
-
182
- ### Response Methods Examples
183
-
184
- The response object supports multiple ways to send responses:
185
-
186
- ```javascript
187
- // Different response types
188
- export default async function(request, response) {
189
- // JSON response
190
- response.json({ message: 'Hello World' });
191
-
192
- // HTML response
193
- response.html('<h1>Hello World</h1>');
194
-
195
- // Text response
196
- response.text('Hello World');
197
-
198
- // Status code chaining
199
- response.status(201).json({ created: true });
200
-
201
- // Custom headers
202
- response.set('X-Custom-Header', 'value').json({ data: 'test' });
203
-
204
- // Redirect
205
- response.redirect('/login');
206
-
207
- // Set cookies
208
- response.cookie('session', 'abc123', { httpOnly: true }).json({ success: true });
209
- }
210
- ```
211
-
212
- ## Configuration
213
-
214
- To configure the server create a `.config.json` within the root directory of your server (`public` in the start example [above](#getting-started)).
215
-
216
- This json file can have any of the following properties, any property not defined will use the "Default Config".
217
-
218
- - [allowedMimes](#allowedmimes)
219
- - [disallowedRegex](#disallowedregex)
220
- - [customRoutes](#customroutes)
221
- - [routeFiles](#routefiles)
222
- - [noRescanPaths](#norescanpaths)
223
- - [maxRescanAttempts](#maxrescanattempts)
224
- - [middleware](#middleware)
225
-
226
- ## Middleware
227
-
228
- Kempo Server includes a powerful middleware system that allows you to add functionality like authentication, logging, CORS, compression, and more. Middleware runs before your route handlers and can modify requests, responses, or handle requests entirely.
229
-
230
- ### Built-in Middleware
231
-
232
- #### CORS
233
- Enable Cross-Origin Resource Sharing for your API:
234
-
235
- ```json
236
- {
237
- "middleware": {
238
- "cors": {
239
- "enabled": true,
240
- "origin": "*",
241
- "methods": ["GET", "POST", "PUT", "DELETE"],
242
- "headers": ["Content-Type", "Authorization"]
243
- }
244
- }
245
- }
246
- ```
247
-
248
- #### Compression
249
- Automatically compress responses with gzip:
250
-
251
- ```json
252
- {
253
- "middleware": {
254
- "compression": {
255
- "enabled": true,
256
- "threshold": 1024
257
- }
258
- }
259
- }
260
- ```
261
-
262
- #### Rate Limiting
263
- Limit requests per client to prevent abuse:
264
-
265
- ```json
266
- {
267
- "middleware": {
268
- "rateLimit": {
269
- "enabled": true,
270
- "maxRequests": 100,
271
- "windowMs": 60000,
272
- "message": "Too many requests"
273
- }
274
- }
275
- }
276
- ```
277
-
278
- #### Security Headers
279
- Add security headers to all responses:
280
-
281
- ```json
282
- {
283
- "middleware": {
284
- "security": {
285
- "enabled": true,
286
- "headers": {
287
- "X-Content-Type-Options": "nosniff",
288
- "X-Frame-Options": "DENY",
289
- "X-XSS-Protection": "1; mode=block"
290
- }
291
- }
292
- }
293
- }
294
- ```
295
-
296
- #### Request Logging
297
- Log requests with configurable detail:
298
-
299
- ```json
300
- {
301
- "middleware": {
302
- "logging": {
303
- "enabled": true,
304
- "includeUserAgent": true,
305
- "includeResponseTime": true
306
- }
307
- }
308
- }
309
- ```
310
-
311
- ### Custom Middleware
312
-
313
- Create your own middleware by writing JavaScript files and referencing them in your config:
314
-
315
- ```json
316
- {
317
- "middleware": {
318
- "custom": ["./middleware/auth.js", "./middleware/analytics.js"]
319
- }
320
- }
321
- ```
322
-
323
- #### Custom Middleware Example
324
-
325
- ```javascript
326
- // middleware/auth.js
327
- export default (config) => {
328
- return async (req, res, next) => {
329
- const token = req.headers.authorization;
330
-
331
- if (!token) {
332
- req.user = null;
333
- return await next();
334
- }
335
-
336
- try {
337
- // Verify JWT token (example)
338
- const user = verifyToken(token);
339
- req.user = user;
340
- req.permissions = await getUserPermissions(user.id);
341
-
342
- // Add helper methods
343
- req.hasPermission = (permission) => req.permissions.includes(permission);
344
-
345
- } catch (error) {
346
- req.user = null;
347
- }
348
-
349
- await next();
350
- };
351
- };
352
- ```
353
-
354
- #### Using Enhanced Requests in Routes
355
-
356
- Your route files can now access the enhanced request object:
357
-
358
- ```javascript
359
- // api/user/profile/GET.js
360
- export default async (req, res, params) => {
361
- if (!req.user) {
362
- return res.status(401).json({ error: 'Authentication required' });
363
- }
364
-
365
- if (!req.hasPermission('user:read')) {
366
- return res.status(403).json({ error: 'Insufficient permissions' });
367
- }
368
-
369
- const profile = await getUserProfile(req.user.id);
370
- res.json(profile);
371
- };
372
- ```
373
-
374
- ### Middleware Order
375
-
376
- Middleware executes in this order:
377
- 1. Built-in middleware (cors, compression, rateLimit, security, logging)
378
- 2. Custom middleware (in the order listed in config)
379
- 3. Your route handlers
380
-
381
- ### Route Interception
382
-
383
- Middleware can intercept and handle routes completely, useful for authentication endpoints:
384
-
385
- ```javascript
386
- // middleware/auth-routes.js
387
- export default (config) => {
388
- return async (req, res, next) => {
389
- const url = new URL(req.url, `http://${req.headers.host}`);
390
-
391
- // Handle login endpoint
392
- if (req.method === 'POST' && url.pathname === '/auth/login') {
393
- const credentials = await req.json();
394
- const token = await authenticateUser(credentials);
395
-
396
- if (token) {
397
- return res.json({ token, success: true });
398
- } else {
399
- return res.status(401).json({ error: 'Invalid credentials' });
400
- }
401
- }
402
-
403
- await next();
404
- };
405
- };
406
- ```
407
-
408
- ### allowedMimes
409
-
410
- An object mapping file extensions to their MIME types. Files with extensions not in this list will not be served.
411
-
412
- ```json
413
- {
414
- "allowedMimes": {
415
- "html": "text/html",
416
- "css": "text/css",
417
- "js": "application/javascript",
418
- "json": "application/json",
419
- "png": "image/png",
420
- "jpg": "image/jpeg"
421
- }
422
- }
423
- ```
424
-
425
- ### disallowedRegex
426
-
427
- An array of regular expressions that match paths that should never be served. This provides security by preventing access to sensitive files.
428
-
429
- ```json
430
- {
431
- "disallowedRegex": [
432
- "^/\\..*",
433
- "\\.env$",
434
- "\\.config$",
435
- "password"
436
- ]
437
- }
438
- ```
439
-
440
- ### routeFiles
441
-
442
- An array of filenames that should be treated as route handlers and executed as JavaScript modules.
443
-
444
- ```json
445
- {
446
- "routeFiles": [
447
- "GET.js",
448
- "POST.js",
449
- "PUT.js",
450
- "DELETE.js",
451
- "index.js"
452
- ]
453
- }
454
- ```
455
-
456
- ### noRescanPaths
457
-
458
- An array of regex patterns for paths that should not trigger a file system rescan. This improves performance for common static assets.
459
-
460
- ```json
461
- {
462
- "noRescanPaths": [
463
- "/favicon\\.ico$",
464
- "/robots\\.txt$",
465
- "\\.map$"
466
- ]
467
- }
468
- ```
469
-
470
- ### customRoutes
471
-
472
- An object mapping custom route paths to file paths. Useful for aliasing or serving files from outside the document root.
473
-
474
- **Basic Routes:**
475
- ```json
476
- {
477
- "customRoutes": {
478
- "/vendor/bootstrap.css": "./node_modules/bootstrap/dist/css/bootstrap.min.css",
479
- "/api/status": "./status.js"
480
- }
481
- }
482
- ```
483
-
484
- **Wildcard Routes:**
485
- Wildcard routes allow you to map entire directory structures using the `*` wildcard:
486
-
487
- ```json
488
- {
489
- "customRoutes": {
490
- "kempo/*": "./node_modules/kempo/dust/*",
491
- "assets/*": "./static-files/*",
492
- "docs/*": "./documentation/*"
493
- }
494
- }
495
- ```
496
-
497
- With wildcard routes:
498
- - `kempo/styles.css` would serve `./node_modules/kempo/dust/styles.css`
499
- - `assets/logo.png` would serve `./static-files/logo.png`
500
- - `docs/readme.md` would serve `./documentation/readme.md`
501
-
502
- The `*` wildcard matches any single path segment (anything between `/` characters). Multiple wildcards can be used in a single route pattern.
503
-
504
- ### maxRescanAttempts
505
-
506
- The maximum number of times to attempt rescanning the file system when a file is not found. Defaults to 3.
507
-
508
- ```json
509
- {
510
- "maxRescanAttempts": 3
511
- }
512
- ```
513
-
514
- ## Features
515
-
516
- - **Zero Dependencies** - No external dependencies required
517
- - **File-based Routing** - Routes are defined by your directory structure
518
- - **Dynamic Routes** - Support for parameterized routes with square bracket syntax
519
- - **Wildcard Routes** - Map entire directory structures with wildcard patterns
520
- - **Middleware System** - Built-in and custom middleware support for authentication, logging, CORS, and more
521
- - **Request Object** - Request handling with built-in body parsing
522
- - **Response Object** - Response handling with chainable methods
523
- - **Multiple HTTP Methods** - Support for GET, POST, PUT, DELETE, and more
524
- - **Static File Serving** - Automatically serves static files with proper MIME types
525
- - **HTML Routes** - Support for both JavaScript and HTML route handlers
526
- - **Query Parameters** - Easy access to URL query parameters
527
- - **Configurable** - Customize behavior with a simple JSON config file
528
- - **Security** - Built-in protection against serving sensitive files plus security headers middleware
529
- - **Performance** - Smart file system caching, rescan optimization, and optional compression
530
-
531
- ## Examples
532
-
533
- ### Simple API Route
534
-
535
- ```javascript
536
- // api/hello/GET.js
537
- export default async function(request, response) {
538
- const { name } = request.query; // Access query parameters like ?name=John
539
-
540
- const message = name ? `Hello ${name}!` : 'Hello World!';
541
-
542
- response.json({ message });
543
- }
544
- ```
545
-
546
- ### Dynamic User Profile Route
547
-
548
- ```javascript
549
- // api/users/[id]/GET.js
550
- export default async function(request, response) {
551
- const { id } = request.params; // Access route parameters
552
- const { includeProfile } = request.query; // Access query parameters
553
-
554
- // Simulate database lookup
555
- const user = {
556
- id: id,
557
- name: `User ${id}`,
558
- email: `user${id}@example.com`
559
- };
560
-
561
- if (includeProfile === 'true') {
562
- user.profile = {
563
- bio: `Bio for user ${id}`,
564
- joinDate: '2024-01-01'
565
- };
566
- }
567
-
568
- response.json(user);
569
- }
570
- ```
571
-
572
- ### JSON API Route
573
-
574
- ```javascript
575
- // api/users/[id]/POST.js
576
- export default async function(request, response) {
577
- const { id } = request.params;
578
-
579
- try {
580
- const userData = await request.json(); // Parse JSON body easily
581
-
582
- // Update user in database
583
- const updatedUser = {
584
- id: id,
585
- ...userData,
586
- updatedAt: new Date().toISOString()
587
- };
588
-
589
- response.json(updatedUser);
590
- } catch (error) {
591
- response.status(400).json({ error: 'Invalid JSON' });
592
- }
593
- }
594
- ```
595
-
596
- ### Form Handling Route
597
-
598
- ```javascript
599
- // contact/POST.js
600
- export default async function(request, response) {
601
- try {
602
- const body = await request.text(); // Get raw text body
603
- const formData = new URLSearchParams(body);
604
- const name = formData.get('name');
605
- const email = formData.get('email');
606
-
607
- // Process form data...
608
-
609
- response.html('<h1>Thank you for your message!</h1>');
610
- } catch (error) {
611
- response.status(400).html('<h1>Error processing form</h1>');
612
- }
613
- }
614
- ```
615
-
616
- ## Command Line Options
617
-
618
- Kempo Server supports several command line options to customize its behavior:
619
-
620
- - `--root <path>` - Set the document root directory (required)
621
- - `--port <number>` - Set the port number (default: 3000)
622
- - `--host <address>` - Set the host address (default: localhost)
623
- - `--verbose` - Enable verbose logging
624
-
625
- ```bash
626
- kempo-server --root public --port 8080 --host 0.0.0.0 --verbose
627
- ```
628
-
629
- ## Testing
630
-
631
- This project uses the Kempo Testing Framework. Tests live in the `tests/` folder and follow these naming conventions:
632
-
633
- - `[name].node-test.js` — Node-only tests
634
- - `[name].browser-test.js` — Browser-only tests
635
- - `[name].test.js` — Runs in both environments
636
-
637
- ### Run tests
638
-
639
- Using npm scripts:
640
-
641
- ```bash
642
- npm run tests # Run all tests (Node + Browser)
643
- npm run tests:node # Run Node tests only
644
- npm run tests:browser # Run Browser tests only
645
- npm run tests:gui # Start the GUI test runner
646
- ```
647
-
648
- For advanced usage (filters, flags, GUI options), see:
649
- https://github.com/dustinpoissant/kempo-testing-framework
650
-
1
+ # Kempo Server
2
+
3
+ A lightweight, zero-dependency, file based routing server.
4
+
5
+ ## Getting Started
6
+
7
+ 1. Install the npm package.
8
+
9
+ ```bash
10
+ npm install kempo-server
11
+ ```
12
+
13
+ 2. Add it to your `package.json` scripts, use the `--root` flag to tell it where the root of your site is.
14
+
15
+ ```
16
+ {
17
+ ...
18
+ "scripts": {
19
+ "start": "kempo-server --root public"
20
+ }
21
+ ...
22
+ }
23
+ ```
24
+
25
+ 3. Run it in your terminal.
26
+
27
+ ```bash
28
+ npm run start
29
+ ```
30
+
31
+ ## Routes
32
+
33
+ A route is a request to a directory that will be handled by a file. To define routes, create the directory structure to the route and create a file with the name of the method that this file will handle. For example `GET.js` will handle the `GET` requests, `POST.js` will handle the `POST` requests and so on. Use `index.js` to handle all request types.
34
+
35
+ The Javascript file must have a **default** export that is the function that will handle the request. It will be passed a `request`, `response`, and `params` arguments (See [Dynamic Routes](#dynamic-routes) below).
36
+
37
+ For example this directory structure:
38
+
39
+ ```
40
+ my/
41
+ ├─ route/
42
+ │ ├─ GET.js
43
+ │ ├─ POST.js
44
+ ├─ other/
45
+ │ ├─ route/
46
+ │ │ ├─ GET.js
47
+ ```
48
+
49
+ Would be used to handle `GET my/route/`, `POST my/route/` and `GET my/other/route/`
50
+
51
+ ### HTML Routes
52
+
53
+ Just like JS files, HTML files can be used to define a route. Use `GET.html`, `POST.html`, etc... to define files that will be served when that route is requested.
54
+
55
+ ```
56
+ my/
57
+ ├─ route/
58
+ │ ├─ GET.js
59
+ │ ├─ POST.js
60
+ ├─ other/
61
+ │ ├─ route/
62
+ │ │ ├─ GET.js
63
+ │ ├─ POST.html
64
+ │ ├─ GET.html
65
+ ```
66
+
67
+ ### `index` fallbacks
68
+
69
+ `index.js` or `index.html` will be used as a fallback for all routes if a *method* file is not defined. In the above examples we do not have any routes defined for `DELETE`, `PUT` `PATCH`, etc... so lets use an `index.js` and `index.html` to be a "catch-all" for all the methods we have not created handlers for.
70
+
71
+ ```
72
+ my/
73
+ ├─ route/
74
+ │ ├─ GET.js
75
+ │ ├─ POST.js
76
+ │ ├─ index.js
77
+ ├─ other/
78
+ │ ├─ route/
79
+ │ │ ├─ GET.js
80
+ │ │ ├─ index.js
81
+ │ ├─ POST.html
82
+ │ ├─ GET.html
83
+ │ ├─ index.html
84
+ ├─ index.html
85
+ ```
86
+
87
+ ## Dynamic Routes
88
+
89
+ A dynamic route is a route with a "param" in its path. To define the dynamic parts of the route just wrap the directory name in square brackets. For example if you wanted to get a users profile, or perform CRUD operations on a user you might create the following directory structure.
90
+
91
+ ```
92
+ api/
93
+ ├─ user/
94
+ │ ├─ [id]/
95
+ │ │ ├─ [info]/
96
+ │ │ │ ├─ GET.js
97
+ │ │ │ ├─ DELETE.js
98
+ │ │ │ ├─ PUT.js
99
+ │ │ │ ├─ POST.js
100
+ │ │ ├─ GET.js
101
+ ```
102
+
103
+ When a request is made to `/api/user/123/info`, the route file `api/user/[id]/[info]/GET.js` would be executed and receive a request object with `request.params` containing `{ id: "123", info: "info" }`.
104
+
105
+ ## Request Object
106
+
107
+ Kempo Server provides a request object that makes working with HTTP requests easier:
108
+
109
+ ### Properties
110
+ - `request.params` - Route parameters from dynamic routes (e.g., `{ id: "123", info: "info" }`)
111
+ - `request.query` - Query string parameters as an object (e.g., `{ page: "1", limit: "10" }`)
112
+ - `request.path` - The pathname of the request URL
113
+ - `request.method` - HTTP method (GET, POST, etc.)
114
+ - `request.headers` - Request headers object
115
+ - `request.url` - Full request URL
116
+
117
+ ### Methods
118
+ - `await request.json()` - Parse request body as JSON
119
+ - `await request.text()` - Get request body as text
120
+ - `await request.body()` - Get raw request body as string
121
+ - `await request.buffer()` - Get request body as Buffer
122
+ - `request.get(headerName)` - Get specific header value
123
+ - `request.is(type)` - Check if content-type contains specified type
124
+
125
+ ### Example Route File
126
+
127
+ Here's an example of what a route file might look like:
128
+
129
+ ```javascript
130
+ // api/user/[id]/GET.js
131
+ export default async function(request, response) {
132
+ const { id } = request.params;
133
+ const { includeDetails } = request.query;
134
+
135
+ // Fetch user data from database
136
+ const userData = await getUserById(id);
137
+
138
+ if (!userData) {
139
+ return response.status(404).json({ error: 'User not found' });
140
+ }
141
+
142
+ response.json(userData);
143
+ }
144
+ ```
145
+
146
+ ### POST Request Example
147
+
148
+ ```javascript
149
+ // api/user/[id]/POST.js
150
+ export default async function(request, response) {
151
+ const { id } = request.params;
152
+
153
+ try {
154
+ const updateData = await request.json();
155
+
156
+ // Update user in database
157
+ const updatedUser = await updateUser(id, updateData);
158
+
159
+ response.json(updatedUser);
160
+ } catch (error) {
161
+ response.status(400).json({ error: 'Invalid JSON' });
162
+ }
163
+ }
164
+ ```
165
+
166
+ ## Response Object
167
+
168
+ Kempo Server also provides a response object that makes sending responses easier:
169
+
170
+ ### Methods
171
+ - `response.json(object)` - Send JSON response with automatic content-type
172
+ - `response.send(data)` - Send response (auto-detects content type)
173
+ - `response.html(htmlString)` - Send HTML response
174
+ - `response.text(textString)` - Send plain text response
175
+ - `response.status(code)` - Set status code (chainable)
176
+ - `response.set(field, value)` - Set header (chainable)
177
+ - `response.type(contentType)` - Set content type (chainable)
178
+ - `response.redirect(url, statusCode)` - Redirect to URL
179
+ - `response.cookie(name, value, options)` - Set cookie
180
+ - `response.clearCookie(name, options)` - Clear cookie
181
+
182
+ ### Response Methods Examples
183
+
184
+ The response object supports multiple ways to send responses:
185
+
186
+ ```javascript
187
+ // Different response types
188
+ export default async function(request, response) {
189
+ // JSON response
190
+ response.json({ message: 'Hello World' });
191
+
192
+ // HTML response
193
+ response.html('<h1>Hello World</h1>');
194
+
195
+ // Text response
196
+ response.text('Hello World');
197
+
198
+ // Status code chaining
199
+ response.status(201).json({ created: true });
200
+
201
+ // Custom headers
202
+ response.set('X-Custom-Header', 'value').json({ data: 'test' });
203
+
204
+ // Redirect
205
+ response.redirect('/login');
206
+
207
+ // Set cookies
208
+ response.cookie('session', 'abc123', { httpOnly: true }).json({ success: true });
209
+ }
210
+ ```
211
+
212
+ ## Configuration
213
+
214
+ To configure the server create a `.config.json` within the root directory of your server (`public` in the start example [above](#getting-started)).
215
+
216
+ This json file can have any of the following properties, any property not defined will use the "Default Config".
217
+
218
+ - [allowedMimes](#allowedmimes)
219
+ - [disallowedRegex](#disallowedregex)
220
+ - [customRoutes](#customroutes)
221
+ - [routeFiles](#routefiles)
222
+ - [noRescanPaths](#norescanpaths)
223
+ - [maxRescanAttempts](#maxrescanattempts)
224
+ - [middleware](#middleware)
225
+
226
+ ## Middleware
227
+
228
+ Kempo Server includes a powerful middleware system that allows you to add functionality like authentication, logging, CORS, compression, and more. Middleware runs before your route handlers and can modify requests, responses, or handle requests entirely.
229
+
230
+ ### Built-in Middleware
231
+
232
+ #### CORS
233
+ Enable Cross-Origin Resource Sharing for your API:
234
+
235
+ ```json
236
+ {
237
+ "middleware": {
238
+ "cors": {
239
+ "enabled": true,
240
+ "origin": "*",
241
+ "methods": ["GET", "POST", "PUT", "DELETE"],
242
+ "headers": ["Content-Type", "Authorization"]
243
+ }
244
+ }
245
+ }
246
+ ```
247
+
248
+ #### Compression
249
+ Automatically compress responses with gzip:
250
+
251
+ ```json
252
+ {
253
+ "middleware": {
254
+ "compression": {
255
+ "enabled": true,
256
+ "threshold": 1024
257
+ }
258
+ }
259
+ }
260
+ ```
261
+
262
+ #### Rate Limiting
263
+ Limit requests per client to prevent abuse:
264
+
265
+ ```json
266
+ {
267
+ "middleware": {
268
+ "rateLimit": {
269
+ "enabled": true,
270
+ "maxRequests": 100,
271
+ "windowMs": 60000,
272
+ "message": "Too many requests"
273
+ }
274
+ }
275
+ }
276
+ ```
277
+
278
+ #### Security Headers
279
+ Add security headers to all responses:
280
+
281
+ ```json
282
+ {
283
+ "middleware": {
284
+ "security": {
285
+ "enabled": true,
286
+ "headers": {
287
+ "X-Content-Type-Options": "nosniff",
288
+ "X-Frame-Options": "DENY",
289
+ "X-XSS-Protection": "1; mode=block"
290
+ }
291
+ }
292
+ }
293
+ }
294
+ ```
295
+
296
+ #### Request Logging
297
+ Log requests with configurable detail:
298
+
299
+ ```json
300
+ {
301
+ "middleware": {
302
+ "logging": {
303
+ "enabled": true,
304
+ "includeUserAgent": true,
305
+ "includeResponseTime": true
306
+ }
307
+ }
308
+ }
309
+ ```
310
+
311
+ ### Custom Middleware
312
+
313
+ Create your own middleware by writing JavaScript files and referencing them in your config:
314
+
315
+ ```json
316
+ {
317
+ "middleware": {
318
+ "custom": ["./middleware/auth.js", "./middleware/analytics.js"]
319
+ }
320
+ }
321
+ ```
322
+
323
+ #### Custom Middleware Example
324
+
325
+ ```javascript
326
+ // middleware/auth.js
327
+ export default (config) => {
328
+ return async (req, res, next) => {
329
+ const token = req.headers.authorization;
330
+
331
+ if (!token) {
332
+ req.user = null;
333
+ return await next();
334
+ }
335
+
336
+ try {
337
+ // Verify JWT token (example)
338
+ const user = verifyToken(token);
339
+ req.user = user;
340
+ req.permissions = await getUserPermissions(user.id);
341
+
342
+ // Add helper methods
343
+ req.hasPermission = (permission) => req.permissions.includes(permission);
344
+
345
+ } catch (error) {
346
+ req.user = null;
347
+ }
348
+
349
+ await next();
350
+ };
351
+ };
352
+ ```
353
+
354
+ #### Using Enhanced Requests in Routes
355
+
356
+ Your route files can now access the enhanced request object:
357
+
358
+ ```javascript
359
+ // api/user/profile/GET.js
360
+ export default async (req, res, params) => {
361
+ if (!req.user) {
362
+ return res.status(401).json({ error: 'Authentication required' });
363
+ }
364
+
365
+ if (!req.hasPermission('user:read')) {
366
+ return res.status(403).json({ error: 'Insufficient permissions' });
367
+ }
368
+
369
+ const profile = await getUserProfile(req.user.id);
370
+ res.json(profile);
371
+ };
372
+ ```
373
+
374
+ ### Middleware Order
375
+
376
+ Middleware executes in this order:
377
+ 1. Built-in middleware (cors, compression, rateLimit, security, logging)
378
+ 2. Custom middleware (in the order listed in config)
379
+ 3. Your route handlers
380
+
381
+ ### Route Interception
382
+
383
+ Middleware can intercept and handle routes completely, useful for authentication endpoints:
384
+
385
+ ```javascript
386
+ // middleware/auth-routes.js
387
+ export default (config) => {
388
+ return async (req, res, next) => {
389
+ const url = new URL(req.url, `http://${req.headers.host}`);
390
+
391
+ // Handle login endpoint
392
+ if (req.method === 'POST' && url.pathname === '/auth/login') {
393
+ const credentials = await req.json();
394
+ const token = await authenticateUser(credentials);
395
+
396
+ if (token) {
397
+ return res.json({ token, success: true });
398
+ } else {
399
+ return res.status(401).json({ error: 'Invalid credentials' });
400
+ }
401
+ }
402
+
403
+ await next();
404
+ };
405
+ };
406
+ ```
407
+
408
+ ### allowedMimes
409
+
410
+ An object mapping file extensions to their MIME types. Files with extensions not in this list will not be served.
411
+
412
+ ```json
413
+ {
414
+ "allowedMimes": {
415
+ "html": "text/html",
416
+ "css": "text/css",
417
+ "js": "application/javascript",
418
+ "json": "application/json",
419
+ "png": "image/png",
420
+ "jpg": "image/jpeg"
421
+ }
422
+ }
423
+ ```
424
+
425
+ ### disallowedRegex
426
+
427
+ An array of regular expressions that match paths that should never be served. This provides security by preventing access to sensitive files.
428
+
429
+ ```json
430
+ {
431
+ "disallowedRegex": [
432
+ "^/\\..*",
433
+ "\\.env$",
434
+ "\\.config$",
435
+ "password"
436
+ ]
437
+ }
438
+ ```
439
+
440
+ ### routeFiles
441
+
442
+ An array of filenames that should be treated as route handlers and executed as JavaScript modules.
443
+
444
+ ```json
445
+ {
446
+ "routeFiles": [
447
+ "GET.js",
448
+ "POST.js",
449
+ "PUT.js",
450
+ "DELETE.js",
451
+ "index.js"
452
+ ]
453
+ }
454
+ ```
455
+
456
+ ### noRescanPaths
457
+
458
+ An array of regex patterns for paths that should not trigger a file system rescan. This improves performance for common static assets.
459
+
460
+ ```json
461
+ {
462
+ "noRescanPaths": [
463
+ "/favicon\\.ico$",
464
+ "/robots\\.txt$",
465
+ "\\.map$"
466
+ ]
467
+ }
468
+ ```
469
+
470
+ ### customRoutes
471
+
472
+ An object mapping custom route paths to file paths. Useful for aliasing or serving files from outside the document root.
473
+
474
+ **Basic Routes:**
475
+ ```json
476
+ {
477
+ "customRoutes": {
478
+ "/vendor/bootstrap.css": "./node_modules/bootstrap/dist/css/bootstrap.min.css",
479
+ "/api/status": "./status.js"
480
+ }
481
+ }
482
+ ```
483
+
484
+ **Wildcard Routes:**
485
+ Wildcard routes allow you to map entire directory structures using the `*` wildcard:
486
+
487
+ ```json
488
+ {
489
+ "customRoutes": {
490
+ "kempo/*": "./node_modules/kempo/dust/*",
491
+ "assets/*": "./static-files/*",
492
+ "docs/*": "./documentation/*"
493
+ }
494
+ }
495
+ ```
496
+
497
+ With wildcard routes:
498
+ - `kempo/styles.css` would serve `./node_modules/kempo/dust/styles.css`
499
+ - `assets/logo.png` would serve `./static-files/logo.png`
500
+ - `docs/readme.md` would serve `./documentation/readme.md`
501
+
502
+ The `*` wildcard matches any single path segment (anything between `/` characters). Multiple wildcards can be used in a single route pattern.
503
+
504
+ ### maxRescanAttempts
505
+
506
+ The maximum number of times to attempt rescanning the file system when a file is not found. Defaults to 3.
507
+
508
+ ```json
509
+ {
510
+ "maxRescanAttempts": 3
511
+ }
512
+ ```
513
+
514
+ ## Features
515
+
516
+ - **Zero Dependencies** - No external dependencies required
517
+ - **File-based Routing** - Routes are defined by your directory structure
518
+ - **Dynamic Routes** - Support for parameterized routes with square bracket syntax
519
+ - **Wildcard Routes** - Map entire directory structures with wildcard patterns
520
+ - **Middleware System** - Built-in and custom middleware support for authentication, logging, CORS, and more
521
+ - **Request Object** - Request handling with built-in body parsing
522
+ - **Response Object** - Response handling with chainable methods
523
+ - **Multiple HTTP Methods** - Support for GET, POST, PUT, DELETE, and more
524
+ - **Static File Serving** - Automatically serves static files with proper MIME types
525
+ - **HTML Routes** - Support for both JavaScript and HTML route handlers
526
+ - **Query Parameters** - Easy access to URL query parameters
527
+ - **Configurable** - Customize behavior with a simple JSON config file
528
+ - **Security** - Built-in protection against serving sensitive files plus security headers middleware
529
+ - **Performance** - Smart file system caching, rescan optimization, and optional compression
530
+
531
+ ## Examples
532
+
533
+ ### Simple API Route
534
+
535
+ ```javascript
536
+ // api/hello/GET.js
537
+ export default async function(request, response) {
538
+ const { name } = request.query; // Access query parameters like ?name=John
539
+
540
+ const message = name ? `Hello ${name}!` : 'Hello World!';
541
+
542
+ response.json({ message });
543
+ }
544
+ ```
545
+
546
+ ### Dynamic User Profile Route
547
+
548
+ ```javascript
549
+ // api/users/[id]/GET.js
550
+ export default async function(request, response) {
551
+ const { id } = request.params; // Access route parameters
552
+ const { includeProfile } = request.query; // Access query parameters
553
+
554
+ // Simulate database lookup
555
+ const user = {
556
+ id: id,
557
+ name: `User ${id}`,
558
+ email: `user${id}@example.com`
559
+ };
560
+
561
+ if (includeProfile === 'true') {
562
+ user.profile = {
563
+ bio: `Bio for user ${id}`,
564
+ joinDate: '2024-01-01'
565
+ };
566
+ }
567
+
568
+ response.json(user);
569
+ }
570
+ ```
571
+
572
+ ### JSON API Route
573
+
574
+ ```javascript
575
+ // api/users/[id]/POST.js
576
+ export default async function(request, response) {
577
+ const { id } = request.params;
578
+
579
+ try {
580
+ const userData = await request.json(); // Parse JSON body easily
581
+
582
+ // Update user in database
583
+ const updatedUser = {
584
+ id: id,
585
+ ...userData,
586
+ updatedAt: new Date().toISOString()
587
+ };
588
+
589
+ response.json(updatedUser);
590
+ } catch (error) {
591
+ response.status(400).json({ error: 'Invalid JSON' });
592
+ }
593
+ }
594
+ ```
595
+
596
+ ### Form Handling Route
597
+
598
+ ```javascript
599
+ // contact/POST.js
600
+ export default async function(request, response) {
601
+ try {
602
+ const body = await request.text(); // Get raw text body
603
+ const formData = new URLSearchParams(body);
604
+ const name = formData.get('name');
605
+ const email = formData.get('email');
606
+
607
+ // Process form data...
608
+
609
+ response.html('<h1>Thank you for your message!</h1>');
610
+ } catch (error) {
611
+ response.status(400).html('<h1>Error processing form</h1>');
612
+ }
613
+ }
614
+ ```
615
+
616
+ ## Command Line Options
617
+
618
+ Kempo Server supports several command line options to customize its behavior:
619
+
620
+ - `--root <path>` - Set the document root directory (required)
621
+ - `--port <number>` - Set the port number (default: 3000)
622
+ - `--host <address>` - Set the host address (default: localhost)
623
+ - `--verbose` - Enable verbose logging
624
+
625
+ ```bash
626
+ kempo-server --root public --port 8080 --host 0.0.0.0 --verbose
627
+ ```
628
+
629
+ ## Testing
630
+
631
+ This project uses the Kempo Testing Framework. Tests live in the `tests/` folder and follow these naming conventions:
632
+
633
+ - `[name].node-test.js` — Node-only tests
634
+ - `[name].browser-test.js` — Browser-only tests
635
+ - `[name].test.js` — Runs in both environments
636
+
637
+ ### Run tests
638
+
639
+ Using npm scripts:
640
+
641
+ ```bash
642
+ npm run tests # Run all tests (Node + Browser)
643
+ npm run tests:node # Run Node tests only
644
+ npm run tests:browser # Run Browser tests only
645
+ npm run tests:gui # Start the GUI test runner
646
+ ```
647
+
648
+ For advanced usage (filters, flags, GUI options), see:
649
+ https://github.com/dustinpoissant/kempo-testing-framework
650
+