SI 679 Week 02 — Express Routing & Middleware, Unit Testing Source: https://umich-mia.atlassian.net/wiki/external/YmI3NWJkMDkzOGJmNDMyZjkyYzdlZWYwYTQ4MDU4Y2U Author: Mark W Newman. Page last updated Sept. 15, 2026. Captured from the fully rendered public Confluence page Sept. 29, 2026. This is a detailed extraction of the rendered page, including every exercise requirement. It is not a verbatim transcript of lecture prose. HEADINGS AND GOALS Today's Goals: TypeScript + Express; deeper Express routing; middleware; HTTP headers/status codes; end-to-end testing with Supertest. Setting up TypeScript Routing in Express Express Routers Now You Try (query filtering) Course Announcements HTTP Headers Express Middleware Why Middleware? (logging, access control, error handling) Error Handling and Status Codes Now You Try (status, GET by ID, PATCH) E2E Testing with Supertest Splitting the app from the server Your first test The entire Supertest API, more or less One subtle thing: tests leak into each other Now You Try (automated test cases) PROJECT SETUP Create week2-router, npm init -y, npm pkg set type="module", npm install express@latest. Install development dependencies typescript, @types/node, @types/express, and tsx. Create tsconfig.json via npx tsc --init, then replace it with compiler settings: target ES2022; module and moduleResolution NodeNext; rootDir src; outDir dist; types ["node"]; strict, esModuleInterop, forceConsistentCasingInFileNames, skipLibCheck true; include ["src"]. Internal TypeScript imports still name the generated .js extension. Add scripts: dev="tsx watch src/index.ts", typecheck="tsc --noEmit", build="tsc", start="node dist/index.js". tsx executes TypeScript without typechecking; run npm run typecheck separately. Add node_modules and dist to .gitignore. BOOKS MODEL, SERVICE, ROUTER In src/types.ts, define const enum BookStatus (AVAILABLE, CHECKED_OUT, LOST), Book interface with numeric id, title, author, numeric year, and status; BookFilter is string | number. In src/books-service.ts, start with an in-memory catalog: Book[]; addBook pushes a Book. In src/index.ts, configure Express on port 6790, express.json(), and POST /books. The POST reads title/author/year from req.body, gives the book default AVAILABLE status and Date.now() id, persists it through addBook, and responds with res.json(book). Test this with a raw JSON POST in a Week 02 Postman collection. Create src/books-router.ts using express.Router(). Move POST /books to router POST /, apply express.json() at router scope, and mount app.use('/books', booksRouter) in index.ts. The public endpoint remains /books. Add getAllBooks() to service and router GET /, returning all books as JSON. Test POST then GET; in-memory catalog survives only until server restart. NOW YOU TRY #1 — QUERY FILTERING Accept the Week 2 NYT Classroom50 assignment, clone its repo, npm install, then implement and create Postman requests for query-string filtering on GET /books. Allow exact matches by title, author, and year; when more than one query term appears, AND them together. Examples: /books?title=The%20Hobbit returns all books titled The Hobbit; /books?author=J.R.R.%20Tolkien returns that author's books; /books?author=J.R.R.%20Tolkien&year=1937 returns only books satisfying both. Push whatever is done by the end of class time. HTTP HEADERS AND MIDDLEWARE LECTURE An HTTP request and response each have a start line, headers, blank line, and body. Start line carries method/protocol or protocol/status; headers carry metadata. Add console.log(req.headers) to GET /books, compare Postman and browser requests, then add a custom request header My-Header-Field: Woot! in Postman and inspect it. Header keys are case insensitive and shown lowercase in the Node request object. The source's sample output says my-own-header rather than My-Header-Field; teach the concept rather than treating that example as an exact contract. express.json() is middleware: it parses JSON before the handler so req.body is available. App-level app.use applies to all following routes; router-level booksRouter.use applies only to that router. Common middleware purposes here are logging, access control, and error handling. Middleware receives req, res, next; call next() to continue, or end the response. If it does neither, the request hangs. Register middleware before the routes it should affect. Logging example: add a logger(req,res,next) in books-router.ts, console.log method, /books plus req.path, and hostname; call next(); attach with booksRouter.use(logger). Access-control example: make catalog a let variable; removeBook(id) filters it. Add DELETE / on the books router, reading req.body.id and responding 200. Create checkAuth middleware using req.headers.authorization, expecting Bearer SI679. Valid token calls next(); absent/wrong token returns 403. Add checkAuth as a route-specific middleware argument to DELETE, and test no token, Bearer SI679, and Bearer SI999 in Postman. Middleware order for the route is express.json(), logger(), then checkAuth(). For POST /books, demonstrate [checkAuth, validateBookParams] as an ordered middleware array. validateBookParams checks title and author are present/nonblank; invalid input returns status 400 with 'Book data must include non-blank "title" and "author" fields.' Test valid body + valid auth, bad auth, and valid auth + bad body. The page's common status table: 200 OK; 201 Created (typical successful POST); 400 Bad Request; 401 Unauthorized (usually unauthenticated); 403 Forbidden (typically authenticated but lacking access); 404 Not Found (missing route or resource); 500 Internal Server Error. Add a four-argument error handler (err, req, res, next) to index.ts after the router. It returns 500 and a message/stack trace in the lecture example. It must be registered last so it can catch errors thrown in earlier routes. Add GET /badroute that throws Error('This is a bad route') and test the handler. A stack trace in a client response is illustrative lecture code, not a recommended production pattern. NOW YOU TRY #2 — STATUS, LOOKUP, PATCH In the existing week02-nyt repo, update with the lecture's index.ts, books-router.ts, books-service.ts while preserving earlier query filtering. Then: 1. Change POST /books to return the appropriate successful creation status (201 from the page's status table). 2. Implement GET /books/:id; return the matching book when found and 404 when absent. 3. Implement PATCH /books. It requires token-based auth and returns 403 for missing/invalid auth. Its JSON body must contain an id and at least one field to change. A valid request changes the book and returns 200; an invalid request returns 400. Push what is complete by the end of class time. SUPERTEST / VITEST LECTURE Supertest sends requests to an Express app from test code and exposes response status, headers, parsed JSON body, and raw text. The page calls this backend end-to-end testing and notes that HW1's starter tests use Supertest. Separate app construction from server listening: src/app.ts creates Express, mounts routers and middleware, then exports app; src/index.ts imports app and invokes app.listen(6790). This permits testing without opening a listening port. Install dev dependencies vitest, supertest, @types/supertest. Add scripts test="vitest run" and test:watch="vitest". Create src/__tests__/books.test.ts. A first test imports describe/expect/it from vitest, request from supertest, and app; GET /books should return 200 and []. Run npm test. Temporarily break the GET handler to observe a red test, then restore it and confirm green. Core Supertest calls taught: .get/.post/.delete(path), .send(body) for JSON, .set(header,value), await to send. Read res.status, res.body for JSON, and res.text for raw text. The second lecture test sets Authorization: Bearer SI679, POSTs The Hobbit by J.R.R. Tolkien (year 1937), asserts status 200 in that intermediate lecture version, then GETs /books and asserts length 1. Tests can leak state because the module-level catalog array persists between tests. Moving POST before the empty-catalog GET makes the latter fail. Add _resetCatalog() to books-service.ts and invoke it via Vitest beforeEach. Leading underscore denotes a test-only helper; HW1 has _resetOrders(). NOW YOU TRY #3 — AUTOMATED TEST CASES In the same week02-nyt repo, set up app.ts, test file, and _resetCatalog() as above. Write as many of these tests as possible and push: 1. POST /books without a token returns 403; wrong token Bearer SI999 also returns 403. 2. POST /books with blank title returns 400, with response text mentioning 'non-blank'. Assert res.text because this route uses send(), not json(). 3. Test query filtering from Now You Try #1: POST two books by different authors, GET /books?author=..., assert exactly one result and the correct book. 4. DELETE needs a token: without one, status 403; with one, POST a book, delete using the returned id, then GET and assert the catalog is empty. COURSE ANNOUNCEMENTS SHOWN ON THE PAGE The instructor listed About Me Slide, About Me Questionnaire, Week 0 TypeScript basics, and Week 1 NYT as already due. HW1 was announced as covering Week 1 and Week 2 and due the following Monday at 11:59pm. Treat these as historical page announcements, not current deadlines. SOURCE QUIRKS The lecture's displayed full books-router.ts under the logging example repeats an import pair. The custom-header narrative and output use different header names. These appear to be page example mistakes; the route and middleware concepts are clear.