Deployment
Food Tracker (React + Express)
Chapter 11 · Deployment
Every prior chapter ran two separate processes — Vite's dev server and Express — talking across the CORS boundary Chapter 1 set up. A real deployment collapses that back down to one.
Building the Client
Serving the Build From Express
app.get("*", ...) matches every path, including /api/items. If the catch-all were registered before the API routers, every single API request would be swallowed by it, silently returning index.html instead of JSON — a real, subtle bug with no error message anywhere, just API calls that mysteriously "stop working" the moment static serving is added. The API routes must always be registered first.
Why the Catch-All Route Exists At All
React Router (or any client-side router) handles navigation entirely in the browser — a URL like /history never actually exists as a real file on the server. Without the catch-all, refreshing the page on /history would hit Express directly, find no matching route, and return a 404. app.get("*", ...) always returns index.html instead, letting React Router take over and render the correct view client-side once the page loads.
Environment Configuration and Process Management
A plain node server/index.js process exits the moment it crashes, and doesn't restart on its own. A process manager like pm2 keeps the server running, restarting it automatically on a crash or a server reboot — the Node equivalent of what a platform's own process supervisor would otherwise provide:
TLS Termination
Chapter 4's own getUserMedia requires HTTPS in production — no exception. Express itself doesn't handle TLS certificates; the standard approach is a reverse proxy (nginx, matching the pattern already covered on this site in Nginx In Depth) sitting in front of the Node process, terminating HTTPS and forwarding plain HTTP internally — the same general shape Food Tracker (Django)'s own deployment chapter used with gunicorn sitting behind a real web server, just with Node in place of gunicorn.
Where This Course Is Headed
One chapter left: a capstone tying every chapter into one complete, working app.
Hands-On Exercises
Explain exactly what would happen to a request for /api/items if the catch-all route were registered before the API routers instead of after, and why no error would appear anywhere to signal the problem.
📄 View solutionExplain why the catch-all route needs to exist at all, given that React Router already handles client-side navigation.
📄 View solutionExplain the difference between this chapter's own SQLite production concern (ephemeral storage) and Food Tracker (Django)'s own SQLite production concern (concurrency), and why each course names a different risk as the more relevant one.
📄 View solutionChapter 11 Quick Reference
- Build: npm run build produces client/dist/, served via express.static
- Order matters: API routes must be registered before the catch-all app.get("*", ...), or it swallows every API request
- The catch-all's job: returns index.html for any unmatched path, letting React Router handle client-side navigation on refresh
- Honest cost: this course hand-configures what Firebase Hosting provided automatically (SPA rewrite, HTTPS) for the Firebase sibling
- Process management: pm2 restarts the server automatically on crash or reboot
- TLS: a reverse proxy (nginx) in front of Node, the same general shape as the Django sibling's gunicorn-behind-a-web-server model
- Real gotcha: better-sqlite3's single database file needs a persistent volume — an ephemeral filesystem silently loses all data
- Next chapter: Capstone