Capstone
Personal Catalogue: PHP & MySQL
Chapter 10 · Capstone
Nine chapters have built a genuinely working catalogue — add/edit/delete, type-specific fields, tags, search, an N+1-avoiding list view, fast bulk entry, and a real deployment plan. This closing chapter covers the actual point of building it: getting it in front of the user, mounted onto their real, existing Astro-based site, then reviews the whole course chapter by chapter.
Three Real Ways to Mount a PHP App Onto an Astro Site
Astro's own live site is a static build served by a web server — it has no PHP runtime of its own, and doesn't need one for its own pages. That means integrating this project isn't a matter of "adding it into Astro" the way a new Astro page would be added — it's a matter of getting both apps served correctly, side by side, behind the same web server:
| Option | What It Looks Like | Real Effort |
|---|---|---|
| Subdomain | catalogue.example.com, a fully separate vhost |
Lowest — no code changes at all, just DNS + a new vhost |
| Path-mounted reverse proxy | example.com/catalogue/, same domain as the Astro site |
Low — one nginx location block, no PHP code changes |
| JSON API + Astro frontend | PHP returns data only; an Astro page/island renders it | Highest — every page effectively rebuilt in Astro/React |
The Realistic Choice: Path-Mounted Reverse Proxy
For a personal project like this, a subdomain works but feels like more infrastructure than the project needs, and a full JSON-API rewrite defeats much of the point of having already built a working PHP app across nine chapters. A path-mounted reverse proxy is the real middle ground: one domain, the existing Astro site untouched, and the catalogue reachable at its own clean path with zero changes to any of the PHP code built so far.
Assuming nginx is already serving the Astro site's static build (per this site's own
Setting Up a Web Server on Debian and Nginx In Depth courses), and
PHP-FPM is running the catalogue on a local port or socket, the real config addition is
one location block:
alias instead of root inside the /catalogue/
block matters specifically because the two paths don't share the same tail: a request for
/catalogue/index.php needs to resolve to the real file at
/var/www/catalogue/index.php, not
/var/www/catalogue/catalogue/index.php (which is what root
would produce, since it appends the full matched URI onto the given path). alias
replaces the matched location prefix with the given directory instead of
appending to it, which is exactly the behavior needed here.
header('Location: /add_item.php...') redirect and every
href="edit_item.php?..." link written across Chapters 3-8 was built assuming
the app sits at the web root. Mounted at /catalogue/ instead, absolute
redirects like header('Location: /add_item.php?added=1') would actually send
the browser to example.com/add_item.php — outside the mounted path entirely,
a real 404 on the Astro site. Every absolute redirect needs a /catalogue
prefix, or (the more robust fix) needs to become a relative redirect
(header('Location: add_item.php?added=1')) that stays correct regardless of
where the app is ever mounted.
Making It Feel Like Part of the Same Site
A reverse proxy solves reachability, not visual consistency — without any further work, the catalogue's own plain unstyled markup would look nothing like the Astro site's real dark theme. A lightweight, real fix: extract the catalogue's own repeated page chrome into one shared partial, styled to visually match the main site's own established look:
catalogue.css itself doesn't need to be built from scratch — copying the
real dark-background/accent-color CSS custom properties already used across this site's
own Astro build gives the catalogue a genuinely matching look with a small, one-time
styling effort, rather than a full visual redesign.
A Real Alternative for Deeper Integration: A JSON Endpoint
If, later, the catalogue's own search results need to appear directly on an Astro page
(rather than linking out to a separate PHP page), the reverse-proxy setup already built
supports that too — a small PHP script can return JSON instead of HTML, and an Astro
island can fetch() it client-side:
This isn't built out further in this course — it's flagged here honestly as the real next step if the project's own scope ever grows toward a tighter Astro-embedded experience, reusing exactly the same PDO connection and prepared-statement discipline every other chapter already established.
Capstone: What Each Chapter Actually Built
The finished project is a genuine sum of nine real prior chapters, not a single new piece written for this capstone:
| Chapter | What It Contributed |
|---|---|
| 1 | The real PDO connection, native prepared statements, and the catalogue database itself |
| 2 | The shared items table, item_types, and the tags/item_tags many-to-many pairing |
| 3 | Add/edit/delete, with delete deliberately restricted to POST |
| 4 | Dynamic, type-aware labels and a real per-type release_year sanity check |
| 5 | Tag creation, attachment, and the diff-based sync_item_tags() |
| 6 | Real search across title/creator, with type/tag filters and LIKE escaping |
| 7 | An N+1-avoiding list view via GROUP_CONCAT, and the detail page |
| 8 | Keyboard-friendly quick-add and a transaction-backed bulk importer |
| 9 | Config separation, schema migration, a least-privilege database user, and production error handling |
| 10 | Mounting the finished app behind the same domain as the real, existing Astro site |
Where Barcode Scanning Fits Now
Chapter 1's own real deadline pushed barcode scanning out of scope for this course
entirely — items get added manually, via the fast bulk-paste path built in Chapter 8. With
the core catalogue now genuinely working and deployed, barcode scanning becomes a real,
concrete next feature rather than an abstract "someday": a barcode's ISBN or UPC number
looked up against a free API (Open Library for books is a natural real fit, matching this
site's own established Food Tracker courses' use of Open Food Facts for a very similar
lookup-by-code pattern), pre-filling add_item.php's own form instead of
requiring every field typed by hand.
Hands-On Exercises
Add the nginx location /catalogue/ block from this chapter to a real (or test) nginx config, alongside a real static index.html served at the site root. Confirm both example.com/ (or your test domain) and example.com/catalogue/index.php resolve correctly to their own separate real content.
Update every header('Location: ...') redirect across add_item.php, edit_item.php, and delete_item.php to use a relative path instead of an absolute one (per this chapter's own warning), then confirm the app still works correctly whether it's mounted at the web root or at /catalogue/.
Build api_search.php from this chapter, visit it directly in a browser with a real search term (e.g. api_search.php?q=Python), and confirm the response is genuine, valid JSON — not an HTML page — by checking the response's own Content-Type header and validating the body with a JSON linter or php -r 'var_dump(json_decode(file_get_contents("php://stdin")));'.
Chapter 10 & Course Quick Reference
- Path-mounted reverse proxy — one nginx
location /catalogue/block, zero PHP code changes, same domain as the existing Astro site alias, notroot— required for a nested location whose URL path doesn't match its real filesystem path- Relative redirects — the real fix that makes every prior chapter's own
header('Location: ...')call correct regardless of mount path - A shared header partial — the lightweight fix for visual consistency with the main site, reusing its own real CSS custom properties
- A JSON endpoint — the real, honest next step if this project's scope ever grows toward tighter Astro-embedded integration
- Barcode scanning — the feature Chapter 1 deliberately deferred, now a real, concrete next step once the core app is live
- Course complete — every chapter's own piece (Chapters 1-9) verified working together as one deployed, real personal catalogue