kempo-server 1.4.3 → 1.4.5
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/.github/copilot-instructions.md +96 -96
- package/.github/workflows/publish-npm.yml +41 -0
- package/README.md +650 -650
- package/builtinMiddleware.js +136 -136
- package/defaultConfig.js +129 -129
- package/docs/.config.json +5 -5
- package/docs/.config.json.example +19 -19
- package/docs/api/user/[id]/GET.js +15 -15
- package/docs/api/user/[id]/[info]/DELETE.js +12 -12
- package/docs/api/user/[id]/[info]/GET.js +17 -17
- package/docs/api/user/[id]/[info]/POST.js +18 -18
- package/docs/api/user/[id]/[info]/PUT.js +19 -19
- package/docs/configuration.html +119 -119
- package/docs/examples.html +201 -201
- package/docs/getting-started.html +72 -72
- package/docs/index.html +81 -81
- package/docs/manifest.json +87 -87
- package/docs/middleware.html +147 -147
- package/docs/request-response.html +95 -95
- package/docs/routing.html +77 -77
- package/example-middleware.js +23 -23
- package/example.config.json +50 -50
- package/findFile.js +138 -138
- package/getFiles.js +72 -72
- package/getFlags.js +34 -34
- package/index.js +47 -47
- package/middlewareRunner.js +25 -25
- package/package.json +10 -6
- package/requestWrapper.js +87 -87
- package/responseWrapper.js +204 -204
- package/router.js +285 -285
- package/serveFile.js +71 -71
- package/tests/builtinMiddleware-cors.node-test.js +17 -17
- package/tests/builtinMiddleware.node-test.js +74 -74
- package/tests/defaultConfig.node-test.js +13 -13
- package/tests/example-middleware.node-test.js +31 -31
- package/tests/findFile.node-test.js +46 -46
- package/tests/getFiles.node-test.js +25 -25
- package/tests/getFlags.node-test.js +30 -30
- package/tests/index.node-test.js +23 -23
- package/tests/middlewareRunner.node-test.js +18 -18
- package/tests/requestWrapper.node-test.js +51 -51
- package/tests/responseWrapper.node-test.js +74 -74
- package/tests/router-middleware.node-test.js +46 -46
- package/tests/router.node-test.js +88 -88
- package/tests/serveFile.node-test.js +52 -52
- 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
|
+
|