Capstone
Personal Catalogue: React, Express & MongoDB
Chapter 10 · Capstone
Chapter 9 got the whole stack running as a real, standalone deployment. This closing chapter does what Chapter 1 always meant it to do: put it somewhere the user actually visits — a real path under their own existing Astro-based site — and lay out a genuine, concrete plan for the one feature deliberately deferred since Chapter 1, barcode scanning.
Three Real Ways to Mount This Behind an Existing Site
| Option | What it involves | Tradeoff |
|---|---|---|
Subdomain (catalogue.existing-site.com) | A separate DNS record and its own nginx server block pointing straight at the unified Express app from Chapter 9 | Simplest possible nginx config; feels like a separate site rather than one integrated experience |
Path-mounted reverse proxy (existing-site.com/catalogue/) | One nginx location block on the existing site's own domain, proxying that one path to the same Express app | Feels like part of the same site; needs two real, verified fixes this chapter builds — covered below |
| JSON-API + Astro-frontend rewrite | Keep only Chapters 1-6's backend; rebuild the UI as new Astro pages/components consuming it directly | The deepest real integration, and the most rework — appropriate only if Astro should become the one true frontend long-term |
The path-mounted option is the one built out in full here — genuinely integrated, without discarding any of the nine chapters of real React frontend already built.
A Real Problem: The Built Assets Don't Know They're Not at the Root
Chapter 9's own npm run build produces an index.html referencing its own JS and CSS
bundles with paths like /assets/index-abc123.js — absolute from whatever domain root the app is
served under. That's correct for Chapter 9's own standalone deployment, and genuinely wrong the moment this
app is mounted at /catalogue/ on someone else's domain: a request for
/assets/index-abc123.js resolves against the domain's own root, not the real location the file
actually lives at, /catalogue/assets/index-abc123.js.
base config option exists specifically for this. Left unset, every generated asset
reference assumes a root deployment; setting it rewrites every one of those references — "JS-imported asset
URLs, CSS url() references, and asset references in your .html files," per Vite's
own documentation — to include the real sub-path prefix.
Rebuilding with this in place produces an index.html referencing
/catalogue/assets/index-abc123.js instead — correct for exactly where the file is actually going
to be requested from once this specific deployment goes live.
A Second Real Problem: API Calls Are Root-Relative, Not Site-Relative
Chapter 9's own API_BASE_URL falls back to an empty string in production, so every fetch call
becomes a path like /api/items — correct for a standalone deployment at the domain root, but
still wrong here for a genuinely different reason than the assets above: /api/items resolves
against the domain's own root regardless of which page made the request, so a request made from
existing-site.com/catalogue/ would still be sent to existing-site.com/api/items —
not existing-site.com/catalogue/api/items, where this app's own API actually needs to be reached
once mounted at a sub-path.
The fix reuses Chapter 9's own mechanism, this time with a real value instead of an empty fallback:
Every fetch call built since Chapter 4 already has the shape
${API_BASE_URL}/api/items — with API_BASE_URL now resolving to
/catalogue, that becomes /catalogue/api/items, matching exactly where the reverse
proxy below is about to make this app's own API actually reachable.
base is a build-time setting controlling where the compiled JavaScript and CSS files
are referenced from. VITE_API_URL is a runtime value controlling where this app's own
fetch calls are sent. Both happen to need the identical /catalogue prefix for this
specific deployment, but fixing one without the other leaves either a broken page (missing assets) or a
broken app (failing API calls) — not both problems solved by a single change.
Building Out the Reverse Proxy
One location block on the existing site's own nginx config, added alongside whatever configuration already serves the rest of that site:
The trailing slash on both location /catalogue/ and proxy_pass http://localhost:4000/;
is what makes nginx strip the /catalogue prefix before forwarding — a request for
/catalogue/api/items reaches Express as a request for exactly /api/items, the
same route Chapter 3 already defined with no changes needed on the Express side at all. The same stripping
applies to a request for /catalogue/assets/index-abc123.js, which reaches Express as
/assets/index-abc123.js — exactly what Chapter 9's own express.static() is already
serving.
With everything mounted this way, the browser only ever sees one domain — Chapter 9's own
ALLOWED_ORIGIN CORS setting can simply be set to https://existing-site.com, and every
request genuinely is same-origin now, exactly the scenario that chapter's own warn-box anticipated without
yet knowing which real shape it would take.
A Real, Concrete Plan for Barcode Scanning
Chapter 1 deferred barcode scanning explicitly rather than pretending it wasn't wanted. With the app actually live, the real plan for adding it later:
- Camera access — a real, actively maintained library like
html5-qrcodehandles reading a barcode from the device camera directly in the browser, without waiting on the nativeBarcodeDetectorAPI's own still-inconsistent cross-browser support. - Book lookup — a scanned ISBN can be sent to Open Library's real, free, no-API-key-required
endpoint,
https://openlibrary.org/isbn/<isbn>.json, to auto-fill title, author, and publisher — reducing Chapter 8's own fast manual entry down to "scan, confirm, save" for the one item type with a clean, verified free lookup source. - Cd/Dvd/Bluray lookup — honestly still an open question. Unlike ISBNs for books, there's no single, universally reliable free UPC-lookup service this course has actually verified for physical media — real research into a genuinely dependable option is the concrete next step here, not something to guess at.
- Where the lookup call should live — a new Express route (something like
GET /api/lookup/isbn/:isbn), matching this project's own established pattern of keeping external API calls server-side rather than scattering direct third-party requests across React components, the same architectural choice Chapters 3 and 6 already made for this app's own data.
Chapter Attribution
| Piece of the finished app | Built in |
|---|---|
| Heterogeneous item schema via Mongoose discriminators | Chapter 2 |
| CRUD API with dynamic dispatch to the right model | Chapter 3 |
| Add-item form, driven by FIELD_CONFIG | Chapter 4 |
| Tag editing via atomic $addToSet/$pull | Chapter 5 |
| Cross-type search, escaped and correctly ordered | Chapter 6 |
| List and detail views, reusing FIELD_CONFIG for display | Chapter 7 |
| Friction reduction: inline errors, focus, live duplicate check | Chapter 8 |
| Unified single-process deployment, API_BASE_URL, PM2, least-privilege MongoDB user | Chapter 9 |
| Path-mounted reverse proxy, Vite base, sub-path API_BASE_URL, barcode-scanning plan | Chapter 10 (this chapter) |
Hands-On Exercises
Set base: '/catalogue/' in vite.config.js and VITE_API_URL=/catalogue in .env.production, rebuild the frontend, and confirm the built index.html's own asset references and every compiled fetch call both correctly include the /catalogue prefix.
📄 View solutionConfigure the nginx location /catalogue/ block against a real running instance of the app (a second local nginx config is fine for testing), and confirm both a page load and a real API request succeed through the proxy, with Express's own server.js completely unchanged from Chapter 9.
📄 View solutionSet only base in vite.config.js without also setting VITE_API_URL (leaving Chapter 9's own empty-string fallback in place), rebuild, and confirm the page itself loads correctly while every API call still fails — demonstrating why the chapter's own warn-box treats these as two separate fixes, not one.
📄 View solutionChapter 10 Quick Reference — Course Complete
- Three mounting options — subdomain, path-mounted reverse proxy (built here), or a full Astro-frontend rewrite
- vite.config.js's base — a build-time fix for asset references, verified against Vite's own documentation
- VITE_API_URL=/catalogue — a runtime fix for fetch calls, reusing Chapter 9's own API_BASE_URL mechanism with a real value instead of an empty fallback
- nginx's trailing-slash prefix-stripping — lets Express's own routes stay completely unchanged from Chapter 9
- No new JSON endpoint needed — unlike the PHP/Django siblings, this app has been API-first since Chapter 3
- Barcode scanning, planned honestly — a real library and a real, verified free ISBN lookup for books; UPC lookup for the other three types named as real, unresolved future research
- This closes the course — 10/10 chapters, matching the shared four-course Personal Catalogue spec from Chapter 1