Routing: Express's Own Wildcard, Hand-Wired

Website Rebuild with Express

Chapter 3 · Routing: Express's Own Wildcard, Hand-Wired

Express registers every route by hand, with no file-based convention at all (Chapter 1's own finding) — including the catch-all this course's own arbitrary-depth routing needs.

Express 5's Current Wildcard Syntax

// routes.js const pool = require('./db'); app.get('/*splat', async (req, res) => { const fullPath = req.params.splat.join('/'); const [rows] = await pool.query('SELECT * FROM pages WHERE full_path = ?', [fullPath]); if (rows.length === 0) { return res.status(404).render('404'); } res.render('page', { page: rows[0] }); });
A real, recent breaking change worth getting right
Express 4's own catch-all used a bare *, with the matched value at req.params[0]. Express 5 upgraded its underlying path-matching library, and bare wildcards no longer work the same way — a named wildcard, *splat, is required instead. req.params.splat is an array of matched segments (e.g. ['programming', 'java'] for /programming/java), not a single joined string — .join('/') is what reconstructs the full path.

Genuinely Different From Every Sibling's Own Capture

Rails' own *path glob captured the whole matched segment as a single string directly. Express 5's *splat captures an array of the individual segments instead — a real, mechanical difference worth naming precisely rather than assuming every wildcard-capture syntax behaves identically just because the underlying goal is the same.

Route Order — the Same Gotcha, One Final Time

Express matches routes in registration order, exactly like every sibling framework in this series. app.get('/*splat', ...) has to be registered last, after any more specific route (like a future admin API route) — otherwise it swallows every request before the more specific route ever gets a chance to match. The identical trap, repeated honestly a final time, not presented as something new to Express.

404 Handling: Not a Gotcha Here, and Here's Why

Astro's gotcha doesn't have an Express equivalent — for a real reason
Astro's own Chapter 3 found a genuine gotcha: a global catch-all silently shadows src/pages/404.astro, a file that looks like it should work automatically but never gets reached. Express has no equivalent surprise, because it never had an automatic, file-based 404 convention to shadow in the first place — res.status(404).render('404') inside the catch-all handler was always going to be written by hand, the same way every other response in this framework is. Nothing silently breaks here, because nothing was ever implicit to begin with.

Six Frameworks' Wildcard Routing, Compared One Final Time

Next.jsDjangoLaravelRailsAstroExpress
Mechanism[...path] folder<path:full_path>{path?} + regex*path glob[...path].astro/*splat
Capture shapeArraySingle stringSingle stringSingle stringSingle stringArray

Hands-On Exercises

Exercise 1

Build the /*splat catch-all route, querying mysql2 directly against the pages table, and confirm a real stored page renders correctly.

📄 View solution
Exercise 2

Log req.params.splat directly and confirm it's a real array of segments, not a single string — contrasting this explicitly with Rails' own *path capture.

📄 View solution
Exercise 3

Register the catch-all before a more specific /admin route, observe it swallow every request to /admin, then fix the ordering.

📄 View solution

Chapter 3 Quick Reference

  • /*splat — Express 5's current named wildcard, replacing Express 4's bare *
  • req.params.splat — an array of matched segments, joined manually into a full path
  • Route order still matters — the same gotcha repeated a final time across the series
  • No 404-shadowing gotcha — Express never had an automatic 404 file to shadow in the first place
  • Next chapter: Views: EJS Templates