Dynamic Content & Forms

Website Rebuild with Express

Chapter 8 · Dynamic Content & Forms

Astro's own request.json() needed zero setup — it's a standard Web API method on any Request object. Express needs one more step before req.body is even populated at all.

Without express.json(): req.body Is Undefined

// no app.use(express.json()) registered yet app.post('/api/pages/:id/title', (req, res) => { console.log(req.body); // undefined — even for a real JSON request body });
More minimal than even Astro, precisely stated
A modern Astro endpoint's request.json() works immediately, on any request, with nothing to configure — it's inherent to the standard Request object itself. Express requires an explicit middleware, express.json(), registered before any route can see a parsed body at all. Without it, req.body isn't an empty object — it's genuinely undefined, and code that assumes otherwise crashes.

Registering It, and Validating with Zod

// server.js app.use(express.json());
// routes/admin.js const { z } = require('zod'); const titleSchema = z.object({ title: z.string().min(1).max(255), }); app.post('/api/pages/:id/title', async (req, res) => { const result = titleSchema.safeParse(req.body); if (!result.success) { return res.status(400).json({ error: result.error.flatten() }); } await pool.query('UPDATE pages SET title = ? WHERE id = ?', [result.data.title, req.params.id]); res.json({ status: 'ok' }); });

Zod — the same choice Astro made — closes the same real gap: nothing in Express validates types, lengths, or required fields on its own.

The Deliberate No-Auth-Check-Yet Gap

Matching every sibling course's own Chapter 8, no authentication check exists yet, to be closed in Chapter 9. Like Astro's own version, there's no conventional place for one to go yet — Express provides nothing resembling before_action or authorize() to even have a placeholder in.

No CSRF Protection — Repeated Honestly a Final Time

The same real, unaddressed gap as Astro
Express ships nothing resembling Rails' protect_from_forgery, Laravel's VerifyCsrfToken, or Django's own CSRF middleware. A cross-origin request could hit this endpoint with no built-in defense at all, the identical gap Astro's own Chapter 8 named and left honestly unsolved.

Request Body Parsing, Compared

AstroExpress
Setup required?None — request.json() is a standard Web API methodExplicit — app.use(express.json()) required
Behavior with no setupWorks by defaultreq.body is genuinely undefined

Hands-On Exercises

Exercise 1

Without registering express.json(), send a real JSON POST request and confirm req.body is undefined, causing naive destructuring code to throw.

📄 View solution
Exercise 2

Add express.json() and the Zod validation, then send a malformed request (e.g. a numeric title) and confirm it's now rejected with a 400 response.

📄 View solution
Exercise 3

Confirm no CSRF protection exists by successfully calling this endpoint from a genuinely different origin, with no same-origin check blocking it.

📄 View solution

Chapter 8 Quick Reference

  • app.use(express.json()) — required just to get req.body populated at all
  • More minimal than Astro — Astro's request.json() needs zero setup; Express's req.body is undefined without one
  • Zod's safeParse — the same deliberate choice Astro made, closing the same real validation gap
  • No CSRF protection — the identical unaddressed gap named in Astro's own Chapter 8
  • Next chapter: Admin Authentication