Skip to content

Commit d7109e1

Browse files
Implement Express middleware exercise with custom and off-the-shelf middleware versions
1 parent 6b91334 commit d7109e1

File tree

17 files changed

+11131
-0
lines changed

17 files changed

+11131
-0
lines changed

README.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,55 @@
11
# Coursework
22

33
Exercises to practice and solidify your understanding of the Decomposition module of the Software Development Course.
4+
5+
## Overview
6+
7+
This repo contains two small Express apps that both implement the same `/subjects` POST endpoint, but in different ways:
8+
9+
- `custom-middlewares/` uses custom middleware for header extraction and JSON array parsing.
10+
- `off-the-shelf-middleware/` uses Express built-in JSON parsing plus a custom validator.
11+
12+
Both apps respond with an authentication message based on the `X-Username` request header and validate that the request body is a JSON array of strings.
13+
14+
## Custom middlewares app
15+
16+
Location: `custom-middlewares/`
17+
18+
Middleware:
19+
20+
- `extractUsername` reads `X-Username` and sets `req.username`.
21+
- `parseJsonArrayBody` manually parses the JSON body and ensures it is an array of strings.
22+
23+
Run tests:
24+
25+
```bash
26+
cd custom-middlewares
27+
npm install
28+
npm test
29+
```
30+
31+
## Off-the-shelf middleware app
32+
33+
Location: `off-the-shelf-middleware/`
34+
35+
Middleware:
36+
37+
- `express.json()` parses JSON bodies.
38+
- `extractUsername` reads `X-Username` and sets `req.username`.
39+
- `validateJsonArray` checks the parsed body is an array of strings.
40+
41+
Run tests:
42+
43+
```bash
44+
cd off-the-shelf-middleware
45+
npm install
46+
npm test
47+
```
48+
49+
## Endpoint behavior
50+
51+
`POST /subjects`
52+
53+
- Body must be a JSON array of strings.
54+
- If `X-Username` is present: responds with `You are authenticated as <name>.`
55+
- If `X-Username` is missing: responds with `You are not authenticated.`

custom-middlewares/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules/

custom-middlewares/app.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
const express = require("express");
2+
const extractUsername = require("./middleware/extractUsername");
3+
const parseJsonArrayBody = require("./middleware/parseJsonArrayBody");
4+
const app = express();
5+
6+
app.post("/subjects",extractUsername,parseJsonArrayBody,
7+
(req, res) => {
8+
9+
const auth = req.username
10+
? `You are authenticated as ${req.username}.`
11+
: `You are not authenticated.`;
12+
13+
res.send(auth);
14+
}
15+
);
16+
17+
module.exports = app;

custom-middlewares/app.test.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
const request = require("supertest");
2+
const app = require("./app");
3+
4+
describe("POST /subjects", () => {
5+
6+
test("responds with authenticated message", async () => {
7+
const response = await request(app)
8+
.post("/subjects")
9+
.set("X-Username", "Ahmed")
10+
.send(["Birds", "Bats"]);
11+
12+
expect(response.statusCode).toBe(200);
13+
expect(response.text).toContain("You are authenticated as Ahmed.");
14+
});
15+
16+
test("responds with not authenticated when header missing", async () => {
17+
const response = await request(app)
18+
.post("/subjects")
19+
.send(["Bees"]);
20+
21+
expect(response.statusCode).toBe(200);
22+
expect(response.text).toContain("You are not authenticated.");
23+
});
24+
25+
test("rejects non-array body", async () => {
26+
const response = await request(app)
27+
.post("/subjects")
28+
.send({ wrong: "format" });
29+
30+
expect(response.statusCode).toBe(400);
31+
});
32+
33+
test("rejects array with non-strings", async () => {
34+
const response = await request(app)
35+
.post("/subjects")
36+
.send([1, 2, 3]);
37+
38+
expect(response.statusCode).toBe(400);
39+
});
40+
41+
});
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
function extractUsername(req, res, next) {
2+
const username = req.get("X-Username");
3+
req.username = username || null;
4+
next();
5+
}
6+
module.exports = extractUsername;
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
2+
function parseJsonArrayBody(req, res, next) {
3+
let data = "";
4+
5+
req.on("data", (chunk) => {
6+
data += chunk;
7+
});
8+
9+
req.on("end", () => {
10+
try {
11+
const parsed = JSON.parse(data);
12+
13+
if (!Array.isArray(parsed)) {
14+
return res.status(400).send("Body must be array");
15+
}
16+
17+
const allStrings = parsed.every(
18+
(item) => typeof item === "string"
19+
);
20+
21+
if (!allStrings) {
22+
return res.status(400).send("Must contain strings");
23+
}
24+
25+
req.body = parsed;
26+
next();
27+
28+
} catch {
29+
res.status(400).send("Invalid JSON");
30+
}
31+
});
32+
}
33+
module.exports = parseJsonArrayBody;

0 commit comments

Comments
 (0)