cpeak 2.2.5 → 2.3.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 CHANGED
@@ -8,6 +8,8 @@ This project is designed to be improved until it's ready for use in complex prod
8
8
 
9
9
  This is an educational project that was started as part of the [Understanding Node.js: Core Concepts](https://www.udemy.com/course/understanding-nodejs-core-concepts/?referralCode=0BC21AC4DD6958AE6A95) course. If you want to learn how to build a framework like this, and get to a point where you can build things like this yourself, check out this course!
10
10
 
11
+ <em>This is the current demo, and the development of the project will begin starting from **September 2025.**</em>
12
+
11
13
  ## Why Cpeak?
12
14
 
13
15
  - **Minimalism**: No unnecessary bloat, with zero dependencies. Just the core essentials you need to build fast and reliable applications.
@@ -24,13 +26,16 @@ This is an educational project that was started as part of the [Understanding No
24
26
  - [Initializing](#initializing)
25
27
  - [Middleware](#middleware)
26
28
  - [Route Handling](#route-handling)
29
+ - [Route Middleware](#route-middleware)
27
30
  - [URL Variables & Parameters](#url-variables--parameters)
28
31
  - [Sending Files](#sending-files)
32
+ - [Redirecting](#redirecting)
29
33
  - [Error Handling](#error-handling)
30
34
  - [Listening](#listening)
31
35
  - [Util Functions](#util-functions)
32
36
  - [serveStatic](#servestatic)
33
37
  - [parseJSON](#parsejson)
38
+ - [render](#render)
34
39
  - [Complete Example](#complete-example)
35
40
  - [Versioning Notice](#versioning-notice)
36
41
 
@@ -114,6 +119,40 @@ server.beforeEach((req, res, next) => {
114
119
  });
115
120
  ```
116
121
 
122
+ ### Route Middleware
123
+
124
+ You can also add middleware functions for a particular route handler like this:
125
+
126
+ ```javascript
127
+ const requireAuth = (req, res, next, handleErr) => {
128
+ // Check if user is logged in, if so then:
129
+ req.test = "this is a test value";
130
+ next();
131
+
132
+ // If user is not logged in:
133
+ return handleErr({ status: 401, message: "Unauthorized" });
134
+ };
135
+
136
+ server.route("get", "/profile", requireAuth, (req, res, handleErr) => {
137
+ console.log(req.test); // this is a test value
138
+ });
139
+ ```
140
+
141
+ You can add as many middleware functions as you want for a route:
142
+
143
+ ```javascript
144
+ server.route(
145
+ "get",
146
+ "/profile",
147
+ requireAuth,
148
+ anotherFunction,
149
+ oneMore,
150
+ (req, res, handleErr) => {
151
+ // your logic
152
+ }
153
+ );
154
+ ```
155
+
117
156
  ### Route Handling
118
157
 
119
158
  You can add new routes like this:
@@ -159,9 +198,17 @@ server.route("get", "/testing", (req, res) => {
159
198
 
160
199
  The file’s binary content will be in the HTTP response body content. Make sure you specify a correct path relative to your CWD (use the `path` module for better compatibility) and also the correct HTTP MIME type for that file.
161
200
 
201
+ ### Redirecting
202
+
203
+ If you want to redirect to a new URL, you can simply do:
204
+
205
+ ```javascript
206
+ res.redirect("https://whatever.com");
207
+ ```
208
+
162
209
  ### Error Handling
163
210
 
164
- If anywhere in your route functions you want to return an error, it's cleaner to pass it to the `handleErr` function like this:
211
+ If anywhere in your route functions or route middleware functions you want to return an error, it's cleaner to pass it to the `handleErr` function like this:
165
212
 
166
213
  ```javascript
167
214
  server.route("get", "/api/document/:title", (req, res, handleErr) => {
@@ -210,6 +257,7 @@ The list of utility functions as of now:
210
257
 
211
258
  - serveStatic
212
259
  - parseJSON
260
+ - render
213
261
 
214
262
  Including any one of them is done like this:
215
263
 
@@ -269,12 +317,50 @@ server.route("put", "/api/user", (req, res) => {
269
317
  });
270
318
  ```
271
319
 
320
+ #### render
321
+
322
+ With this function you can do server side rendering before sending a file to a client. This can be useful for dynamic customization and search engine optimization.
323
+
324
+ First fire it up like this:
325
+
326
+ ```javascript
327
+ server.beforeEach(render());
328
+ ```
329
+
330
+ And then for rendering:
331
+
332
+ ```javascript
333
+ server.route("get", "/", (req, res, next) => {
334
+ return res.render(
335
+ "./public/index.html",
336
+ {
337
+ title: "Page title",
338
+ name: "Allan",
339
+ },
340
+ "text/html"
341
+ );
342
+ });
343
+ ```
344
+
345
+ You can then inject the variables into your file in {{ variable_name }} like this:
346
+
347
+ ```HTML
348
+ <html>
349
+ <head>
350
+ <title>{{ title }}</title>
351
+ </head>
352
+ <body>
353
+ <h1>{{ name }}</h1>
354
+ </body>
355
+ </html>
356
+ ```
357
+
272
358
  ## Complete Example
273
359
 
274
360
  Here you can see all the features that Cpeak offers, in one small piece of code:
275
361
 
276
362
  ```javascript
277
- import cpeak, { serveStatic, parseJSON } from "cpeak";
363
+ import cpeak, { serveStatic, parseJSON, render } from "cpeak";
278
364
 
279
365
  const server = new cpeak();
280
366
 
@@ -284,6 +370,8 @@ server.beforeEach(
284
370
  })
285
371
  );
286
372
 
373
+ server.beforeEach(render());
374
+
287
375
  // For parsing JSON bodies
288
376
  server.beforeEach(parseJSON);
289
377
 
@@ -293,25 +381,56 @@ server.beforeEach((req, res, next) => {
293
381
  next();
294
382
  });
295
383
 
296
- // Adding route handlers
297
- server.route("get", "/api/document/:title", (req, res, handleErr) => {
298
- // Reading URL variables
299
- const title = req.vars.title;
384
+ // A middleware function that can be specified to run before some particular routes
385
+ const testRouteMiddleware = (req, res, next, handleErr) => {
386
+ req.whatever = "some calculated value maybe";
300
387
 
301
- // Reading URL parameters (like /users?filter=active)
302
- const filter = req.params.filter;
388
+ if (req.vars.test !== "something special") {
389
+ return handleErr({ status: 400, message: "an error message" });
390
+ }
303
391
 
304
- // Reading JSON request body
305
- const anything = req.body.anything;
392
+ next();
393
+ };
306
394
 
307
- // Handling errors
308
- if (anything === "not-expected-thing")
309
- return handleErr({ status: 400, message: "Invalid property." });
395
+ // Adding route handlers
396
+ server.route("get", "/", (req, res, next) => {
397
+ return res.render(
398
+ "<path-to-file-relative-to-cwd>",
399
+ {
400
+ test: "some testing value",
401
+ number: "2343242",
402
+ },
403
+ "<mime-type>"
404
+ );
405
+ });
310
406
 
311
- // Sending a JSON response
312
- res.status(200).json({ message: "This is a test response" });
407
+ server.route("get", "/old-url", testRouteMiddleware, (req, res, next) => {
408
+ return res.redirect("/new-url");
313
409
  });
314
410
 
411
+ server.route(
412
+ "get",
413
+ "/api/document/:title",
414
+ testRouteMiddleware,
415
+ (req, res, handleErr) => {
416
+ // Reading URL variables
417
+ const title = req.vars.title;
418
+
419
+ // Reading URL parameters (like /users?filter=active)
420
+ const filter = req.params.filter;
421
+
422
+ // Reading JSON request body
423
+ const anything = req.body.anything;
424
+
425
+ // Handling errors
426
+ if (anything === "not-expected-thing")
427
+ return handleErr({ status: 400, message: "Invalid property." });
428
+
429
+ // Sending a JSON response
430
+ res.status(200).json({ message: "This is a test response" });
431
+ }
432
+ );
433
+
315
434
  // Sending a file response
316
435
  server.route("get", "/file", (req, res) => {
317
436
  // Make sure to specify a correct path and MIME type...
package/lib/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import http from "node:http";
2
2
  import fs from "node:fs/promises";
3
3
 
4
- import { serveStatic, parseJSON } from "./utils/index.js";
4
+ import { serveStatic, parseJSON, render } from "./utils/index.js";
5
5
 
6
6
  class Cpeak {
7
7
  constructor() {
@@ -27,6 +27,13 @@ class Cpeak {
27
27
  return res;
28
28
  };
29
29
 
30
+ // Redirects to a new URL
31
+ res.redirect = (location) => {
32
+ res.writeHead(302, { Location: location });
33
+ res.end();
34
+ return res;
35
+ };
36
+
30
37
  // Send a json data back to the client (for small json data, less than the highWaterMark)
31
38
  res.json = (data) => {
32
39
  // This is only good for bodies that their size is less than the highWaterMark value
@@ -42,9 +49,35 @@ class Cpeak {
42
49
  const params = new URLSearchParams(req.url.split("?")[1]);
43
50
  req.params = Object.fromEntries(params.entries());
44
51
 
45
- // Run all the middleware functions before we run the corresponding route
52
+ // Run all the specific middleware functions for that router only and then run the handler
53
+ const runHandler = (req, res, middleware, cb, index) => {
54
+ // Our exit point...
55
+ if (index === middleware.length) {
56
+ // Call the route handler with the modified req and res objects
57
+ return cb(req, res, (error) => {
58
+ res.setHeader("Connection", "close");
59
+ this.handleErr(error, req, res);
60
+ });
61
+ } else {
62
+ middleware[index](
63
+ req,
64
+ res,
65
+ // The next function
66
+ () => {
67
+ runHandler(req, res, middleware, cb, index + 1);
68
+ },
69
+ // Error handler for a route middleware
70
+ (error) => {
71
+ res.setHeader("Connection", "close");
72
+ this.handleErr(error, req, res);
73
+ }
74
+ );
75
+ }
76
+ };
77
+
78
+ // Run all the middleware functions (beforeEach functions) before we run the corresponding route
46
79
  const runMiddleware = (req, res, middleware, index) => {
47
- // Out exit point...
80
+ // Our exit point...
48
81
  if (index === middleware.length) {
49
82
  const routes = this.routes[req.method.toLowerCase()];
50
83
  if (routes && typeof routes[Symbol.iterator] === "function")
@@ -56,11 +89,7 @@ class Cpeak {
56
89
  const vars = this.#extractVars(route.path, match);
57
90
  req.vars = vars;
58
91
 
59
- // Call the route handler with the modified req and res objects
60
- return route.cb(req, res, (error) => {
61
- res.setHeader("Connection", "close");
62
- this.handleErr(error, req, res);
63
- });
92
+ return runHandler(req, res, route.middleware, route.cb, 0);
64
93
  }
65
94
  }
66
95
 
@@ -79,11 +108,17 @@ class Cpeak {
79
108
  });
80
109
  }
81
110
 
82
- route(method, path, cb) {
111
+ route(method, path, ...args) {
83
112
  if (!this.routes[method]) this.routes[method] = [];
84
113
 
114
+ // The last argument is always our handler
115
+ const cb = args.pop();
116
+
117
+ // Rest will be our middleware functions
118
+ const middleware = args.flat();
119
+
85
120
  const regex = this.#pathToRegex(path);
86
- this.routes[method].push({ path, regex, cb });
121
+ this.routes[method].push({ path, regex, middleware, cb });
87
122
  }
88
123
 
89
124
  beforeEach(cb) {
@@ -132,6 +167,6 @@ class Cpeak {
132
167
  }
133
168
  }
134
169
 
135
- export { serveStatic, parseJSON };
170
+ export { serveStatic, parseJSON, render };
136
171
 
137
172
  export default Cpeak;
@@ -1,4 +1,5 @@
1
1
  import { parseJSON } from "./parseJSON.js";
2
2
  import { serveStatic } from "./serveStatic.js";
3
+ import { render } from "./render.js";
3
4
 
4
- export { serveStatic, parseJSON };
5
+ export { serveStatic, parseJSON, render };
@@ -1,20 +1,28 @@
1
1
  // Parsing JSON
2
2
  const parseJSON = (req, res, next) => {
3
3
  // This is only good for bodies that their size is less than the highWaterMark value
4
- if (req.headers["content-type"] === "application/json") {
5
- let body = "";
6
- req.on("data", (chunk) => {
7
- body += chunk.toString("utf-8");
8
- });
9
4
 
10
- req.on("end", () => {
11
- body = JSON.parse(body);
12
- req.body = body;
13
- return next();
14
- });
15
- } else {
16
- next();
5
+ function isJSON(contentType = "") {
6
+ // Remove any params like "; charset=UTF-8"
7
+ const [type] = contentType.split(";");
8
+ return (
9
+ type.trim().toLowerCase() === "application/json" ||
10
+ /\+json$/i.test(type.trim())
11
+ );
17
12
  }
13
+
14
+ if (!isJSON(req.headers["content-type"])) return next();
15
+
16
+ let body = "";
17
+ req.on("data", (chunk) => {
18
+ body += chunk.toString("utf-8");
19
+ });
20
+
21
+ req.on("end", () => {
22
+ body = JSON.parse(body);
23
+ req.body = body;
24
+ return next();
25
+ });
18
26
  };
19
27
 
20
28
  export { parseJSON };
@@ -0,0 +1,64 @@
1
+ import fs from "node:fs/promises";
2
+
3
+ function renderTemplate(templateStr, data) {
4
+ // Initialize variables
5
+ let result = [];
6
+
7
+ let currentIndex = 0;
8
+
9
+ while (currentIndex < templateStr.length) {
10
+ // Find the next opening placeholder
11
+ const startIdx = templateStr.indexOf("{{", currentIndex);
12
+ if (startIdx === -1) {
13
+ // No more placeholders, push the remaining string
14
+ result.push(templateStr.slice(currentIndex));
15
+ break;
16
+ }
17
+
18
+ // Push the part before the placeholder
19
+ result.push(templateStr.slice(currentIndex, startIdx));
20
+
21
+ // Find the closing placeholder
22
+ const endIdx = templateStr.indexOf("}}", startIdx);
23
+ if (endIdx === -1) {
24
+ // No closing brace found, treat the rest as plain text
25
+ result.push(templateStr.slice(startIdx));
26
+ break;
27
+ }
28
+
29
+ // Extract the variable name
30
+ const varName = templateStr.slice(startIdx + 2, endIdx).trim();
31
+
32
+ // Replace the variable with its value from the data, or use an empty string if not found
33
+ const replacement = data[varName] !== undefined ? data[varName] : "";
34
+
35
+ // Push the replacement to the result array
36
+ result.push(replacement);
37
+
38
+ // Move the index past the current closing placeholder
39
+ currentIndex = endIdx + 2;
40
+ }
41
+
42
+ // Join all parts into a final string
43
+ return result.join("");
44
+ }
45
+
46
+ // Errors to return: recommend to not render files larger than 100KB
47
+ // To Explore: Doing the operation in C++ and return the data as stream back to the client
48
+ // @TODO: remove the file from static map
49
+ // @TODO: escape the string to prevent XSS
50
+ // @TODO: add another {{{ }}} option to not escape the string
51
+ const render = () => {
52
+ return function (req, res, next) {
53
+ res.render = async (path, data, mime) => {
54
+ let fileStr = await fs.readFile(path, "utf-8");
55
+ const finalStr = renderTemplate(fileStr, data);
56
+ res.setHeader("Content-Type", mime);
57
+ res.end(finalStr);
58
+ };
59
+
60
+ next();
61
+ };
62
+ };
63
+
64
+ export { render };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cpeak",
3
- "version": "2.2.5",
3
+ "version": "2.3.0",
4
4
  "description": "A minimal and fast Node.js HTTP framework.",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
package/playground.js ADDED
@@ -0,0 +1,63 @@
1
+ function renderTemplate(templateStr, data) {
2
+ // Initialize variables
3
+ let result = [];
4
+ let currentIndex = 0;
5
+
6
+ while (currentIndex < templateStr.length) {
7
+ // Find the next opening placeholder
8
+ const startIdx = templateStr.indexOf("{{", currentIndex);
9
+ if (startIdx === -1) {
10
+ // No more placeholders, push the remaining string
11
+ result.push(templateStr.slice(currentIndex));
12
+ break;
13
+ }
14
+
15
+ // Push the part before the placeholder
16
+ result.push(templateStr.slice(currentIndex, startIdx));
17
+
18
+ // Find the closing placeholder
19
+ const endIdx = templateStr.indexOf("}}", startIdx);
20
+ if (endIdx === -1) {
21
+ // No closing brace found, treat the rest as plain text
22
+ result.push(templateStr.slice(startIdx));
23
+ break;
24
+ }
25
+
26
+ // Extract the variable name
27
+ const varName = templateStr.slice(startIdx + 2, endIdx).trim();
28
+
29
+ // Replace the variable with its value from the data, or use an empty string if not found
30
+ const replacement = data[varName] !== undefined ? data[varName] : "";
31
+
32
+ // Push the replacement to the result array
33
+ result.push(replacement);
34
+
35
+ // Move the index past the current closing placeholder
36
+ currentIndex = endIdx + 2;
37
+ }
38
+
39
+ // Join all parts into a final string
40
+ return result.join("");
41
+ }
42
+
43
+ // Example usage:
44
+ const templateStr = `
45
+ <!DOCTYPE html>
46
+ <html>
47
+ <head>
48
+ <title>Poster</title>
49
+
50
+ <link rel="stylesheet" href="/styles.css" />
51
+ </head>
52
+ <body style="color: #333">
53
+ <h1>{{ msg1 }} {{ msg2 }}</h1>
54
+
55
+ <!-- <strong>{{msgBar}}</strong> -->
56
+
57
+ <p>
58
+ `;
59
+
60
+ const data = { msg1: "Alice", msg2: 30 };
61
+
62
+ const output = renderTemplate(templateStr, data);
63
+ console.log(output); // Output: "Hello, Alice! You are 30 years old."