Capstone: Integrating the Catalogue Into the Existing Astro Site, and Self-Managed MongoDB vs. Managed Firestore Compared
Personal Catalogue: React & Firebase
Chapter 10 · Capstone
Chapter 9 got the whole app running as a real, standalone Firebase Hosting deployment. This closing chapter puts it somewhere the user actually visits — a real path under their own existing Astro-based site — lays out a genuine, concrete plan for the one feature deferred since Chapter 1, and closes the whole course with an honest, itemized scorecard against its own MongoDB sibling.
Three Real Ways to Mount This — With Firebase Hosting Changing the Menu Slightly
| Option | What it involves | Tradeoff |
|---|---|---|
Subdomain (catalogue.existing-site.com) | Point a DNS record straight at Firebase Hosting via its own custom-domain feature — no reverse proxy needed at all | Simplest possible setup; 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 Firebase Hosting's real URL | Feels like part of the same site; needs the real fixes this chapter builds — covered below |
| A deeper Astro-native rewrite | Keep only Firestore's own schema and Security Rules; rebuild the UI as new Astro pages/components calling the Firestore SDK 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 built out in full here, genuinely integrated without discarding any of the nine chapters of real React frontend already built.
The Real Asset-Path Fix — Identical to the MongoDB Sibling, for the Identical Reason
Chapter 9's own npm run build produces an index.html referencing its own JS and CSS
bundles with absolute paths like /assets/index-abc123.js. That's correct for a standalone
deployment at a domain root, and genuinely wrong once mounted at /catalogue/: the browser's own
subsequent request for that absolute path resolves against the domain's own root, not the real sub-path this
app actually lives under — a request for /assets/index-abc123.js never even reaches nginx's own
/catalogue/ location block, since the request itself doesn't start with that prefix at all. This
is a pure client-side, browser-resolves-absolute-paths problem, with nothing about Firebase Hosting or
Firestore changing it in any way. The fix is the identical one the MongoDB sibling verified against Vite's own
documentation:
Rebuilt with this in place, index.html references /catalogue/assets/index-abc123.js
instead — a path nginx's own /catalogue/ location block genuinely matches, and one Firebase
Hosting itself never needs to know anything special about, since it just serves whatever file that path
resolves to inside its own dist/ upload.
The Runtime Fix the MongoDB Sibling Needed — and This Course Genuinely Doesn't
The MongoDB sibling's own second fix updates VITE_API_URL to a real sub-path value, because its
own fetch calls are built as relative paths that resolve against whatever origin and path the current page
happens to be served from — the browser has no way to know an API call meant for
/catalogue/api/items shouldn't just go to /api/items unless it's told so explicitly.
projectId set back in
Chapter 9's own environment variables), completely independent of whatever domain or path the React app itself
happens to be served from at that moment. Mounting this app at /catalogue/, at a bare subdomain,
or at a completely different domain entirely changes nothing about how Firestore calls resolve — there was
never a same-origin-relative API call here for a sub-path to break in the first place, a direct, concrete
extension of Chapter 9's own "no CORS needed" finding.
Reverse-Proxying to an External, CDN-Fronted Backend — a Genuinely Different Nginx Problem
The MongoDB, PHP, and Django siblings all proxy to a local process on the same machine —
proxy_pass http://localhost:PORT/;, with no real concern about which domain name the request
claims to be for, since a local process doesn't route based on that. Firebase Hosting is a shared,
multi-tenant CDN serving many different Firebase projects from the same infrastructure, and it decides which
project's own content to serve based on the Host header (and, for HTTPS, SNI) presented on the
request:
Host: existing-site.com header (nginx's own real default
when none is set explicitly) would confuse Firebase's own routing entirely — it has no project registered
under that hostname, so the request would fail or resolve to the wrong content. Explicitly rewriting
Host to the real Firebase Hosting domain fixes that. proxy_ssl_server_name on; is
the second, easy-to-miss piece: it makes nginx present the correct SNI hostname during the TLS handshake
itself, which the same shared, multi-tenant infrastructure also depends on to route the connection to the
right backend before a single byte of the actual HTTP request is ever read.
With both in place, the trailing slash on location /catalogue/ and proxy_pass
https://catalogue-prod.web.app/; strips the /catalogue prefix before forwarding, exactly
the same mechanism the MongoDB sibling relies on — a request for /catalogue/assets/index-abc123.js
reaches Firebase Hosting as a request for exactly /assets/index-abc123.js, which its own CDN
already serves correctly with no changes needed on the Firebase Hosting side at all.
Barcode Scanning — Where This Course's Own "No Backend" Streak Finally Ends
Chapter 1 deferred barcode scanning explicitly, and flagged an honest, unresolved question rather than asserting an answer: whether this course could get away with never needing a Cloud Function at all. With the app actually live, that question has a real answer now — no, not once this specific feature is added.
- Camera access — a real, actively maintained library like
html5-qrcodereads a barcode from the device camera directly in the browser, matching the MongoDB sibling's own recommendation exactly, since this piece is pure frontend and has nothing to do with either backend. - Book lookup, and the real reason it needs a Cloud Function — a scanned ISBN can be sent
to Open Library's real, free, no-API-key endpoint to auto-fill title, author, and publisher. Calling it
directly from the browser would work today, but the moment this app ever needs a real, secret-holding API key
for any lookup service, that call has to move server-side — and since this course has no server of its own at
all, its first-ever
onCallCloud Function is what that server-side call would actually become, the exact scenario Chapter 1's own honest open question was waiting to be resolved by. - Cd/Dvd/Bluray lookup — honestly still open, the same real gap the MongoDB sibling names: no single, universally reliable free UPC-lookup service has actually been verified for physical media here. Genuine research is the concrete next step, not something to guess at.
Chapter Attribution
| Piece of the finished app | Built in |
|---|---|
| Firestore document shapes, matched to the MongoDB sibling's own schema | Chapter 2 |
| The client-SDK-only architecture decision, plus real Security Rules | Chapter 3 |
| Add-item form, driven by the shared FIELD_CONFIG | Chapter 4 |
| Tag editing via arrayUnion/arrayRemove, plus the tagsOnlyOnBooks rule | Chapter 5 |
| Fetch-and-filter search, replacing regex entirely | Chapter 6 |
| List and detail views, driven by a live onSnapshot listener | Chapter 7 |
| Friction reduction: focus return, debounced duplicate check | Chapter 8 |
| Firebase Hosting deployment, env-driven project config | Chapter 9 |
| Path-mounted reverse proxy, Vite base, barcode-scanning plan | Chapter 10 (this chapter) |
Self-Managed MongoDB vs. Managed Firestore: the Real Scorecard
| Area | Real outcome across this course |
|---|---|
| Backend of any kind | Firestore wins — no server to write, run, or deploy at all (Ch.3, Ch.9) |
| Per-type schema validation | MongoDB wins — Mongoose discriminators enforce it for free; this course hand-built validateItem() and Security Rules to match (Ch.2, Ch.3) |
| Atomic array updates (tags) | A genuine tie — arrayUnion/arrayRemove match $addToSet/$pull exactly, with no backend route needed on either side to get there (Ch.5) |
| Preventing a field on the wrong document type | MongoDB wins — automatic via discriminator models; this course found the gap the hard way and closed it with a hand-written rule (Ch.5) |
| Text search | MongoDB wins clearly — real regex/$or search vs. no substring search capability at all here, worked around with a client-side fetch-and-filter (Ch.6) |
| Keeping a UI in sync with the database | Firestore wins — onSnapshot() eliminates the manual state-lifting wiring this course's own sibling needs (Ch.7) |
| Production deployment complexity | Firestore wins decisively — no CORS, PM2, or reverse proxy for a backend of its own (Ch.9) |
| Ongoing operating cost model | MongoDB wins on predictability — a self-run instance has no per-document-read billing; every Firestore read is metered, made concrete in Chapter 6's own full-collection search cost |
| Sub-path deployment fixes needed | Firestore needs fewer — only the shared Vite asset-path fix, not a second runtime API-path fix, since Firestore never routes through the app's own current origin (Ch.10) |
Neither column wins outright. What genuinely changed, chapter after chapter, wasn't whether real problems existed — it's where the work to solve them landed: inside a schema class and an Express route for the MongoDB sibling, or inside a hand-written validator and a declarative rules file here. Managed infrastructure removed real operational work this course never had to think about; it also removed guarantees this course had to go rebuild by hand, one genuine gap at a time.
Hands-On Exercises
Set base: '/catalogue/' in vite.config.js, rebuild, and confirm the built index.html's own asset references correctly include the /catalogue prefix — then explain in one sentence why no equivalent runtime environment-variable fix is needed for Firestore calls, unlike the MongoDB sibling's own second fix.
📄 View solutionConfigure the nginx location /catalogue/ block against a real, deployed Firebase Hosting site (a second local nginx config is fine for testing), including the Host header override and proxy_ssl_server_name. Confirm the page loads correctly, then temporarily remove just the Host header line and observe what happens.
📄 View solutionUsing this chapter's own scorecard, pick the single real finding from the whole course you think matters most for a genuinely larger catalogue (tens of thousands of items rather than a few hundred), and explain why — citing the specific chapter it came from.
📄 View solutionChapter 10 Quick Reference — Course Complete
- Three mounting options — a Firebase Hosting custom domain, a path-mounted reverse proxy (built here), or a deeper Astro-native rewrite
- The asset-path fix — identical to the MongoDB sibling's own vite.config base fix, for the identical browser-side reason
- The runtime fix that isn't needed — Firestore never routes through the app's own current origin, so there's no second, API-path-specific fix to make
- A genuinely new nginx problem — proxying to a CDN-fronted external service needs an explicit Host header override and proxy_ssl_server_name, unlike proxying to a local process
- Barcode scanning — the real point where this course's own "no backend" streak ends, needing its first Cloud Function
- The scorecard — real, itemized wins and costs across both variants; no clean overall winner, just different places the real work landed
- This closes the course — 10/10 chapters, matching the shared four-course Personal Catalogue spec from Chapter 1