kempo-server 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,421 @@
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
+ ```json
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 6 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
+
225
+ ### allowedMimes
226
+
227
+ An object mapping file extensions to their MIME types. Files with extensions not in this list will not be served.
228
+
229
+ ```json
230
+ {
231
+ "allowedMimes": {
232
+ "html": "text/html",
233
+ "css": "text/css",
234
+ "js": "application/javascript",
235
+ "json": "application/json",
236
+ "png": "image/png",
237
+ "jpg": "image/jpeg"
238
+ }
239
+ }
240
+ ```
241
+
242
+ ### disallowedRegex
243
+
244
+ An array of regular expressions that match paths that should never be served. This provides security by preventing access to sensitive files.
245
+
246
+ ```json
247
+ {
248
+ "disallowedRegex": [
249
+ "^/\\..*",
250
+ "\\.env$",
251
+ "\\.config$",
252
+ "password"
253
+ ]
254
+ }
255
+ ```
256
+
257
+ ### routeFiles
258
+
259
+ An array of filenames that should be treated as route handlers and executed as JavaScript modules.
260
+
261
+ ```json
262
+ {
263
+ "routeFiles": [
264
+ "GET.js",
265
+ "POST.js",
266
+ "PUT.js",
267
+ "DELETE.js",
268
+ "index.js"
269
+ ]
270
+ }
271
+ ```
272
+
273
+ ### noRescanPaths
274
+
275
+ An array of regex patterns for paths that should not trigger a file system rescan. This improves performance for common static assets.
276
+
277
+ ```json
278
+ {
279
+ "noRescanPaths": [
280
+ "/favicon\\.ico$",
281
+ "/robots\\.txt$",
282
+ "\\.map$"
283
+ ]
284
+ }
285
+ ```
286
+
287
+ ### customRoutes
288
+
289
+ An object mapping custom route paths to file paths. Useful for aliasing or serving files from outside the document root.
290
+
291
+ ```json
292
+ {
293
+ "customRoutes": {
294
+ "/vendor/bootstrap.css": "./node_modules/bootstrap/dist/css/bootstrap.min.css",
295
+ "/api/status": "./status.js"
296
+ }
297
+ }
298
+ ```
299
+
300
+ ### maxRescanAttempts
301
+
302
+ The maximum number of times to attempt rescanning the file system when a file is not found. Defaults to 3.
303
+
304
+ ```json
305
+ {
306
+ "maxRescanAttempts": 3
307
+ }
308
+ ```
309
+
310
+ ## Features
311
+
312
+ - **Zero Dependencies** - No external dependencies required
313
+ - **File-based Routing** - Routes are defined by your directory structure
314
+ - **Dynamic Routes** - Support for parameterized routes with square bracket syntax
315
+ - **Request Object** - Request handling with built-in body parsing
316
+ - **Response Object** - Response handling with chainable methods
317
+ - **Multiple HTTP Methods** - Support for GET, POST, PUT, DELETE, and more
318
+ - **Static File Serving** - Automatically serves static files with proper MIME types
319
+ - **HTML Routes** - Support for both JavaScript and HTML route handlers
320
+ - **Query Parameters** - Easy access to URL query parameters
321
+ - **Configurable** - Customize behavior with a simple JSON config file
322
+ - **Security** - Built-in protection against serving sensitive files
323
+ - **Performance** - Smart file system caching and rescan optimization
324
+
325
+ ## Examples
326
+
327
+ ### Simple API Route
328
+
329
+ ```javascript
330
+ // api/hello/GET.js
331
+ export default async function(request, response) {
332
+ const { name } = request.query; // Access query parameters like ?name=John
333
+
334
+ const message = name ? `Hello ${name}!` : 'Hello World!';
335
+
336
+ response.json({ message });
337
+ }
338
+ ```
339
+
340
+ ### Dynamic User Profile Route
341
+
342
+ ```javascript
343
+ // api/users/[id]/GET.js
344
+ export default async function(request, response) {
345
+ const { id } = request.params; // Access route parameters
346
+ const { includeProfile } = request.query; // Access query parameters
347
+
348
+ // Simulate database lookup
349
+ const user = {
350
+ id: id,
351
+ name: `User ${id}`,
352
+ email: `user${id}@example.com`
353
+ };
354
+
355
+ if (includeProfile === 'true') {
356
+ user.profile = {
357
+ bio: `Bio for user ${id}`,
358
+ joinDate: '2024-01-01'
359
+ };
360
+ }
361
+
362
+ response.json(user);
363
+ }
364
+ ```
365
+
366
+ ### JSON API Route
367
+
368
+ ```javascript
369
+ // api/users/[id]/POST.js
370
+ export default async function(request, response) {
371
+ const { id } = request.params;
372
+
373
+ try {
374
+ const userData = await request.json(); // Parse JSON body easily
375
+
376
+ // Update user in database
377
+ const updatedUser = {
378
+ id: id,
379
+ ...userData,
380
+ updatedAt: new Date().toISOString()
381
+ };
382
+
383
+ response.json(updatedUser);
384
+ } catch (error) {
385
+ response.status(400).json({ error: 'Invalid JSON' });
386
+ }
387
+ }
388
+ ```
389
+
390
+ ### Form Handling Route
391
+
392
+ ```javascript
393
+ // contact/POST.js
394
+ export default async function(request, response) {
395
+ try {
396
+ const body = await request.text(); // Get raw text body
397
+ const formData = new URLSearchParams(body);
398
+ const name = formData.get('name');
399
+ const email = formData.get('email');
400
+
401
+ // Process form data...
402
+
403
+ response.html('<h1>Thank you for your message!</h1>');
404
+ } catch (error) {
405
+ response.status(400).html('<h1>Error processing form</h1>');
406
+ }
407
+ }
408
+ ```
409
+
410
+ ## Command Line Options
411
+
412
+ Kempo Server supports several command line options to customize its behavior:
413
+
414
+ - `--root <path>` - Set the document root directory (required)
415
+ - `--port <number>` - Set the port number (default: 3000)
416
+ - `--host <address>` - Set the host address (default: localhost)
417
+ - `--verbose` - Enable verbose logging
418
+
419
+ ```bash
420
+ kempo-server --root public --port 8080 --host 0.0.0.0 --verbose
421
+ ```
@@ -0,0 +1,93 @@
1
+ export default {
2
+ allowedMimes: {
3
+ html: "text/html",
4
+ htm: "text/html",
5
+ shtml: "text/html",
6
+ css: "text/css",
7
+ xml: "text/xml",
8
+ gif: "image/gif",
9
+ jpeg: "image/jpeg",
10
+ jpg: "image/jpeg",
11
+ js: "application/javascript",
12
+ mjs: "application/javascript",
13
+ json: "application/json",
14
+ webp: "image/webp",
15
+ png: "image/png",
16
+ svg: "image/svg+xml",
17
+ svgz: "image/svg+xml",
18
+ ico: "image/x-icon",
19
+ webm: "video/webm",
20
+ mp4: "video/mp4",
21
+ m4v: "video/mp4",
22
+ ogv: "video/ogg",
23
+ mp3: "audio/mpeg",
24
+ ogg: "audio/ogg",
25
+ wav: "audio/wav",
26
+ woff: "font/woff",
27
+ woff2: "font/woff2",
28
+ ttf: "font/ttf",
29
+ otf: "font/otf",
30
+ eot: "application/vnd.ms-fontobject",
31
+ pdf: "application/pdf",
32
+ txt: "text/plain",
33
+ webmanifest: "application/manifest+json",
34
+ md: "text/markdown",
35
+ csv: "text/csv",
36
+ doc: "application/msword",
37
+ docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
38
+ xls: "application/vnd.ms-excel",
39
+ xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
40
+ ppt: "application/vnd.ms-powerpoint",
41
+ pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
42
+ avif: "image/avif",
43
+ wasm: "application/wasm"
44
+ },
45
+ disallowedRegex: [
46
+ "^/\\..*",
47
+ "\\.config$",
48
+ "\\.env$",
49
+ "\\.git/",
50
+ "\\.htaccess$",
51
+ "\\.htpasswd$",
52
+ "^/node_modules/",
53
+ "^/vendor/",
54
+ "\\.log$",
55
+ "\\.bak$",
56
+ "\\.sql$",
57
+ "\\.ini$",
58
+ "password",
59
+ "config\\.php$",
60
+ "wp-config\\.php$",
61
+ "\\.DS_Store$"
62
+ ],
63
+ routeFiles: [
64
+ 'GET.js',
65
+ 'POST.js',
66
+ 'PUT.js',
67
+ 'DELETE.js',
68
+ 'HEAD.js',
69
+ 'OPTIONS.js',
70
+ 'PATCH.js',
71
+ 'CONNECT.js',
72
+ 'TRACE.js',
73
+ 'index.js'
74
+ ],
75
+ noRescanPaths: [
76
+ "^\\.well-known/",
77
+ "/favicon\\.ico$",
78
+ "/robots\\.txt$",
79
+ "/sitemap\\.xml$",
80
+ "/apple-touch-icon",
81
+ "/android-chrome-",
82
+ "/browserconfig\\.xml$",
83
+ "/manifest\\.json$",
84
+ "\\.map$",
85
+ "/__webpack_hmr$",
86
+ "/hot-update\\.",
87
+ "/sockjs-node/",
88
+ ],
89
+ maxRescanAttempts: 3,
90
+ customRoutes: {
91
+ // Example: "/vendor/bootstrap.css": "./node_modules/bootstrap/dist/css/bootstrap.min.css"
92
+ }
93
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "customRoutes": {
3
+ "/essential.css": "./node_modules/essentialcss/dist/essential.min.css",
4
+ "/essential-hljs.css": "./node_modules/essentialcss/dist/essential-hljs.min.css"
5
+ }
6
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "customRoutes": {
3
+ "/vendor/bootstrap.css": "./node_modules/bootstrap/dist/css/bootstrap.min.css",
4
+ "/vendor/bootstrap.js": "./node_modules/bootstrap/dist/js/bootstrap.min.js",
5
+ "/vendor/jquery.js": "./node_modules/jquery/dist/jquery.min.js"
6
+ },
7
+ "allowedMimes": {
8
+ "woff": "font/woff",
9
+ "woff2": "font/woff2"
10
+ },
11
+ "disallowedRegex": [
12
+ "private/",
13
+ "\\.env$"
14
+ ],
15
+ "noRescanPaths": [
16
+ "/vendor/"
17
+ ],
18
+ "maxRescanAttempts": 3
19
+ }
@@ -0,0 +1,15 @@
1
+ export default async function(request, response) {
2
+ const { id } = request.params;
3
+
4
+ // Example user data
5
+ const userData = {
6
+ id: id,
7
+ profile: {
8
+ name: `${id.charAt(0).toUpperCase()}${id.slice(1)}`,
9
+ joinDate: '2024-01-15',
10
+ posts: 42
11
+ }
12
+ };
13
+
14
+ response.json(userData);
15
+ }
@@ -0,0 +1,12 @@
1
+ export default async function(request, response) {
2
+ const { id, info } = request.params;
3
+
4
+ // Example response for deleting user info
5
+ const result = {
6
+ id: id,
7
+ message: 'User info deleted successfully',
8
+ deletedAt: new Date().toISOString()
9
+ };
10
+
11
+ response.json(result);
12
+ }
@@ -0,0 +1,17 @@
1
+ export default async function(request, response) {
2
+ const { id, info } = request.params;
3
+
4
+ // Example detailed user info
5
+ const userInfo = {
6
+ id: id,
7
+ details: {
8
+ bio: `This is ${id}'s bio`,
9
+ location: 'Earth',
10
+ website: `https://${id}.dev`,
11
+ followers: 123,
12
+ following: 456
13
+ }
14
+ };
15
+
16
+ response.json(userInfo);
17
+ }
@@ -0,0 +1,18 @@
1
+ export default async function(request, response) {
2
+ const { id, info } = request.params;
3
+
4
+ try {
5
+ const updateData = await request.json();
6
+
7
+ // Example response for updating user info
8
+ const updatedUser = {
9
+ id: id,
10
+ message: 'User info updated successfully',
11
+ updatedFields: updateData
12
+ };
13
+
14
+ response.json(updatedUser);
15
+ } catch (error) {
16
+ response.status(400).json({ error: 'Invalid JSON' });
17
+ }
18
+ }
@@ -0,0 +1,19 @@
1
+ export default async function(request, response) {
2
+ const { id, info } = request.params;
3
+
4
+ try {
5
+ const updateData = await request.json();
6
+
7
+ // Example response for replacing user info
8
+ const updatedUser = {
9
+ id: id,
10
+ message: 'User info replaced successfully',
11
+ newData: updateData,
12
+ updatedAt: new Date().toISOString()
13
+ };
14
+
15
+ response.json(updatedUser);
16
+ } catch (error) {
17
+ response.status(400).json({ error: 'Invalid JSON' });
18
+ }
19
+ }