Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
node_modules/
package-lock.json
.env
.DS_Store
*.log
dist/
build/
.vscode/
65 changes: 65 additions & 0 deletions middleware-exercise/README.md
Original file line number Diff line number Diff line change
@@ -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 <PID>
```

## 🧪 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/
```

60 changes: 60 additions & 0 deletions middleware-exercise/off-the-shelf-middleware.js
Original file line number Diff line number Diff line change
@@ -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");
});
16 changes: 16 additions & 0 deletions middleware-exercise/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "middleware-exercise",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"express": "^5.2.1"
}
}
82 changes: 82 additions & 0 deletions middleware-exercise/two-custom-written-middlewares.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
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();
};

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);

let bodyObject;
try {
bodyObject = JSON.parse(bodyString);
} catch (error) {
res.status(400).send("Invalid JSON");
return;
}

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.",
);
}
});
};

// 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");
});
Loading