From edd1159f4a21595812c64c604f522a9feac8c023 Mon Sep 17 00:00:00 2001 From: iswat Date: Sat, 9 May 2026 23:26:01 +0100 Subject: [PATCH 1/8] feat: Add .gitignore for Node.js project - Exclude node_modules/ and package-lock.json from version control - Ignore environment variables (.env) and system files (.DS_Store) - Exclude build artifacts (dist/, build/) and log files (*.log) - Ignore VS Code workspace settings (.vscode/) - Reduce repository size and prevent committing sensitive data --- .gitignore | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..c35aab4a --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +node_modules/ +package-lock.json +.env +.DS_Store +*.log +dist/ +build/ +.vscode/ \ No newline at end of file From 0cfc2d17cea9fa5ee97ddf0fdec5ef71a4cecf4d Mon Sep 17 00:00:00 2001 From: iswat Date: Fri, 5 Jun 2026 19:00:06 +0100 Subject: [PATCH 2/8] Add middleware: X-username header and JSON string-array body parser - Add username middleware that reads X-username header and sets req.username (or null). - Add array middleware that accumulates request bytes, parses JSON, validates it's an array of strings, assigns req.body or returns 400 on invalid input. - Add package.json with Express dependency. --- middleware-exercise/app.js | 43 ++++++++++++++++++++++++++++++++ middleware-exercise/package.json | 15 +++++++++++ 2 files changed, 58 insertions(+) create mode 100644 middleware-exercise/app.js create mode 100644 middleware-exercise/package.json diff --git a/middleware-exercise/app.js b/middleware-exercise/app.js new file mode 100644 index 00000000..16009548 --- /dev/null +++ b/middleware-exercise/app.js @@ -0,0 +1,43 @@ +import express from "express"; + +const usernameMiddleware = (req, res, next) => { + const headerValue = req.get("X-username"); + + if (headerValue) { + req.username = headerValue; + } else { + req.username = null; + } + + next(); +}; + +const arrayMiddleware = (req, res, next) => { + const bodyBytes = []; + + // Every time a piece of data arrives, we put it in our list + req.on("data", (chunk) => { + bodyBytes.push(...chunk); + }); + + // When the whole message has arrived, we process it + req.on("end", () => { + const bodyString = String.fromCharCode(...bodyBytes); + + const bodyObject = JSON.parse(bodyString); + + if ( + Array.isArray(bodyObject) && + bodyObject.every((item) => typeof item === "string") + ) { + req.body = bodyObject; + next(); + } else { + res + .status(400) + .send( + "Invalid request body. Expected a JSON array containing only strings.", + ); + } + }); +}; diff --git a/middleware-exercise/package.json b/middleware-exercise/package.json new file mode 100644 index 00000000..2b7df785 --- /dev/null +++ b/middleware-exercise/package.json @@ -0,0 +1,15 @@ +{ + "name": "middleware-exercise", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "express": "^5.2.1" + } +} From 8d2a02fc4c4c13c4ca9440a02281b2a5a758da9e Mon Sep 17 00:00:00 2001 From: iswat Date: Fri, 5 Jun 2026 20:17:03 +0100 Subject: [PATCH 3/8] Add "type": "module" to package.json Enable ES module syntax (import/export) for the project by setting "type": "module" in package.json. --- middleware-exercise/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/middleware-exercise/package.json b/middleware-exercise/package.json index 2b7df785..8a23acfd 100644 --- a/middleware-exercise/package.json +++ b/middleware-exercise/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "description": "", "main": "index.js", + "type": "module", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, From 65a83cff021af09d19ede8338a1e00ef14b745b5 Mon Sep 17 00:00:00 2001 From: iswat Date: Fri, 5 Jun 2026 20:18:21 +0100 Subject: [PATCH 4/8] Initialize Express app, add PORT env fallback, harden JSON parsing, add route and start server - Create Express app instance and use PORT from process.env with 3000 fallback. - Add try/catch around JSON.parse in array middleware and return 400 on invalid JSON. - Add POST "/" route that composes response using usernameMiddleware and arrayMiddleware. - Start server with app.listen. --- middleware-exercise/app.js | 41 +++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/middleware-exercise/app.js b/middleware-exercise/app.js index 16009548..7f4fb6aa 100644 --- a/middleware-exercise/app.js +++ b/middleware-exercise/app.js @@ -1,5 +1,9 @@ import express from "express"; +const app = express(); + +const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000; + const usernameMiddleware = (req, res, next) => { const headerValue = req.get("X-username"); @@ -24,7 +28,13 @@ const arrayMiddleware = (req, res, next) => { req.on("end", () => { const bodyString = String.fromCharCode(...bodyBytes); - const bodyObject = JSON.parse(bodyString); + let bodyObject; + try { + bodyObject = JSON.parse(bodyString); + } catch (error) { + res.status(400).send("Invalid JSON"); + return; + } if ( Array.isArray(bodyObject) && @@ -41,3 +51,32 @@ const arrayMiddleware = (req, res, next) => { } }); }; + +// Hey Express, whenever a POST request arrives at '/', please call this person first (usernameMiddleware), then this person (arrayMiddleware), then finally do the route logic. +app.post("/", usernameMiddleware, arrayMiddleware, (req, res) => { + let authPart; + if (req.username) { + authPart = `You are authenticated as ${req.username}`; + } else { + authPart = "You are not authenticated"; + } + const MessageCount = req.body.length; + + const messageJoined = req.body.join(","); + + const word = MessageCount <= 1 ? "subject" : "subjects"; + + if (MessageCount > 0) { + res.send( + `${authPart}\n\nYou have requested information about ${MessageCount} ${word}: ${messageJoined}.`, + ); + } else { + res.send( + `${authPart}\n\nYou have requested information about ${MessageCount} ${word}.`, + ); + } +}); + +app.listen(PORT, () => { + console.log("Type your message here"); +}); From e08d8ec98c83cc018efffa8a6218bb952ecdfb71 Mon Sep 17 00:00:00 2001 From: iswat Date: Fri, 5 Jun 2026 20:24:42 +0100 Subject: [PATCH 5/8] Rename app.js to "two-custom-written-middlewares.js" - Rename middleware-exercise/app.js to middleware-exercise/two custom-written middlewares.js - Clarifies that the file contains two custom middleware implementations --- middleware-exercise/{app.js => two-custom-written-middlewares.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename middleware-exercise/{app.js => two-custom-written-middlewares.js} (100%) diff --git a/middleware-exercise/app.js b/middleware-exercise/two-custom-written-middlewares.js similarity index 100% rename from middleware-exercise/app.js rename to middleware-exercise/two-custom-written-middlewares.js From 8402a16eb6b91167368e5142267c4edede0516d9 Mon Sep 17 00:00:00 2001 From: iswat Date: Fri, 5 Jun 2026 20:50:05 +0100 Subject: [PATCH 6/8] Fix pluralization for subject(s) - Change condition in two-custom-written-middlewares.js from MessageCount <= 1 to MessageCount === 1 so "subject" is used only when there is exactly one item (zero now uses "subjects"). --- middleware-exercise/two-custom-written-middlewares.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/middleware-exercise/two-custom-written-middlewares.js b/middleware-exercise/two-custom-written-middlewares.js index 7f4fb6aa..f75c3267 100644 --- a/middleware-exercise/two-custom-written-middlewares.js +++ b/middleware-exercise/two-custom-written-middlewares.js @@ -64,7 +64,7 @@ app.post("/", usernameMiddleware, arrayMiddleware, (req, res) => { const messageJoined = req.body.join(","); - const word = MessageCount <= 1 ? "subject" : "subjects"; + const word = MessageCount === 1 ? "subject" : "subjects"; if (MessageCount > 0) { res.send( From cdcd59663853b1419b4e5f371c2886bed843a065 Mon Sep 17 00:00:00 2001 From: iswat Date: Fri, 5 Jun 2026 20:53:44 +0100 Subject: [PATCH 7/8] Add Express app with username and array-validation middleware - Add off-the-shelf-middleware.js: create Express app and listen on PORT (env fallback to 3000). - Add username middleware (reads X-username header into req.username). - Add validateArray middleware (ensures JSON body is an array of strings; returns 400 on invalid input). - Add POST "/" route that composes a response using the middlewares and starts the server. --- .../off-the-shelf-middleware.js | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 middleware-exercise/off-the-shelf-middleware.js diff --git a/middleware-exercise/off-the-shelf-middleware.js b/middleware-exercise/off-the-shelf-middleware.js new file mode 100644 index 00000000..d9544b5d --- /dev/null +++ b/middleware-exercise/off-the-shelf-middleware.js @@ -0,0 +1,60 @@ +import express from "express"; + +const app = express(); + +const PORT = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000; + +const usernameMiddleware = (req, res, next) => { + const headerValue = req.get("X-username"); + + if (headerValue) { + req.username = headerValue; + } else { + req.username = null; + } + + next(); +}; + +app.use(express.json()); + +const validateArray = (req, res, next) => { + if ( + req.body && + Array.isArray(req.body) && + req.body.every((item) => typeof item === "string") + ) { + next(); + } else { + res.status(400).send("Error message"); + } +}; + +// Hey Express, whenever a POST request arrives at '/', please call this person first (usernameMiddleware), then this person (arrayMiddleware), then finally do the route logic. +app.post("/", usernameMiddleware, validateArray, (req, res) => { + let authPart; + if (req.username) { + authPart = `You are authenticated as ${req.username}`; + } else { + authPart = "You are not authenticated"; + } + const MessageCount = req.body.length; + + const messageJoined = req.body.join(","); + + const word = MessageCount === 1 ? "subject" : "subjects"; + + if (MessageCount > 0) { + res.send( + `${authPart}\n\nYou have requested information about ${MessageCount} ${word}: ${messageJoined}.`, + ); + } else { + res.send( + `${authPart}\n\nYou have requested information about ${MessageCount} ${word}.`, + ); + } +}); + +app.listen(PORT, () => { + console.log("Type your message here"); +}); From 077c598ead8698d1ee66a8cce3bf9ec729ec4166 Mon Sep 17 00:00:00 2001 From: iswat Date: Fri, 5 Jun 2026 21:19:12 +0100 Subject: [PATCH 8/8] Add README for middleware-exercise with run instructions and examples - Add README.md describing how to run the custom and off-the-shelf middleware demos. - Document key concepts: middleware pattern, manual parsing vs express.json, env PORT fallback, and process management. - Include curl examples for valid and invalid requests and note default port 3000. --- middleware-exercise/README.md | 65 +++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 middleware-exercise/README.md diff --git a/middleware-exercise/README.md b/middleware-exercise/README.md new file mode 100644 index 00000000..385bb0d2 --- /dev/null +++ b/middleware-exercise/README.md @@ -0,0 +1,65 @@ +# Middleware Exercise: Custom vs. Off-the-Shelf + +This project demonstrates the implementation of Middleware in Express.js. It explores two ways of handling request data: manually parsing raw data streams and using built-in Express tools. + +## ๐Ÿš€ How to Run + +1. **Install dependencies:** + ```bash + npm install + ``` + +2. **Run the Custom Middleware version:** + ```bash + node "two custom-written middlewares.js" + ``` + +3. **Run the Off-the-Shelf version:** + ```bash + node off-the-shelf-middleware.js + ``` + +*Note: The server defaults to port `3000`. To use a specific port, use: `PORT=4000 node [filename].js`* + +## ๐Ÿ›  Key Concepts Learned + +### 1. The Middleware Pattern +Middleware functions sit between the request and the final route handler. In this project: +- **Username Middleware:** Extracts identity from headers. +- **Validation Middleware:** Ensures the incoming data matches the expected format before the route logic runs. + +### 2. Manual Parsing vs. `express.json()` +- **Custom Parsing:** Uses `req.on("data")` and `req.on("end")` to catch raw bytes, translate them via `String.fromCharCode`, and parse them. +- **Built-in Parsing:** Uses `express.json()`, which requires the client to send the `Content-Type: application/json` header. + +### 3. Environment Variables & Safety +- **Port Fallback:** Used `process.env.PORT` with a fallback to `3000`. +- **Parsing Numbers:** Used `parseInt(..., 10)` to ensure the port string from the environment is converted to a base-10 integer. +- **ES Modules:** Set `"type": "module"` in `package.json` to allow the use of modern `import/export` syntax. + +### 4. Process Management +Learned how to identify and terminate "zombie" processes hanging on a port using: +```bash +sudo lsof -iTCP:3000 -sTCP:LISTEN -Pn +sudo kill -9 +``` + +## ๐Ÿงช Testing with `curl` + +To test the application, use the following commands in your terminal: + +**Valid Request (with Username):** +```bash +curl -X POST -H "Content-Type: application/json" -H "X-Username: Ahmed" -d '["Bees", "Birds"]' http://localhost:3000/ +``` + +**Valid Request (Guest):** +```bash +curl -X POST -H "Content-Type: application/json" -d '["Lizards"]' http://localhost:3000/ +``` + +**Invalid Data (should return 400):** +```bash +curl -X POST -H "Content-Type: application/json" -d 'notjson' http://localhost:3000/ +``` +