Capstone: A Complete, Working Flexible-Routing Site

Website Rebuild with Laravel

Chapter 12 · Capstone: A Complete, Working Flexible-Routing Site

Sam — the same site admin from the Next.js and Django rebuild capstones, reused a third time because it's genuinely the same site being rebuilt a third time — logs into this course's own deployed Laravel version. Every step below draws on a specific earlier chapter, closing with an attribution table and an honest scope note.

Step 1 — Visiting the Live Deployment

Sam opens the site in a browser. It's served by the same Apache instance that's always run this domain, now also handing PHP requests to a Laravel PHP-FPM pool over mod_proxy_fcgi. APP_DEBUG=false means a stray error would show a clean generic page, not a stack trace.

Step 2 — Logging In

At /admin/login, Sam enters the legacy admin credential. Auth::attempt() checks it against the stored bcrypt hash using Laravel's own default hasher — no configuration was ever needed to make this work, since Laravel's default already matched the legacy hash's algorithm. session()->regenerate() issues a fresh session ID.

Step 3 — Building the Five-Level Chain

Through the admin CRUD interface, Sam creates programming, then general-purpose-languages under it, then java, then fundamentals, then chapter-1 — five real levels deep, no longer the illustrative example Chapter 1 opened with. Each save recomputes that page's own full_path from its parent, exactly as Chapter 2's self-referencing Page model and Chapter 6's Eloquent ORM were designed to do.

Step 4 — Viewing the Page

Visiting /programming/general-purpose-languages/java/fundamentals/chapter-1, Chapter 3's {path?} wildcard route resolves the full five-segment path in one route definition, Chapter 4's Blade template renders the page with a breadcrumb built in the Controller rather than the template, and Chapter 5's Vite-bundled dark-theme CSS applies.

Step 5 — Editing the Title, Now Actually Protected

Sam edits the Chapter 1 page's title. Chapter 8's route and UpdatePageTitleRequest handle the submission, with @csrf protecting the form — but this time, authorize() returns Chapter 9's real Auth::check() instead of the placeholder true Chapter 8 deliberately left in place. Logged out, the exact same request would now be refused.

Step 6 — Reorganizing: Paying the Real Cascade Cost

Sam decides fundamentals belongs under a different parent. Chapter 10's movePage() updates fundamentals' own full_path, then updateDescendantPaths() recurses down and updates chapter-1's full_path too — the real, concrete payment of the cascade cost Chapter 2 flagged and deliberately deferred all the way back at the very start of the course.

Step 7 — Attempting to Delete a Page With Children

Sam tries to delete java, which still has fundamentals beneath it. The database's own restrictOnDelete() constraint from Chapter 2 refuses the operation; Chapter 10's try/catch in destroy() turns the resulting QueryException into a plain, readable message instead of a raw error page.

Step 8 — Confirming the Kanji Migration Held Up

Sam visits /japanese/kanji/水. Chapter 7's resolution holds: no application-level slug validator blocks the character the way Django's own SlugField once needed allow_unicode=True to allow, and the database's utf8mb4 charset stores the multi-byte character correctly. The page renders through the same rendering path as every other page on the site — no special-cased kanji route required.

Step 9 — A Quick N+1 Sanity Check

Sam checks the query log while loading the breadcrumb-heavy Chapter 1 page. Chapter 6's with() eager loading is doing its job — one batched query for the ancestor chain, not one query per level, the same fix that was needed after the N+1 pattern was first caught live in Chapter 4's own breadcrumb code.

Step 10 — Confirming the Deploy Itself Is Healthy

Sam confirms npm run build's output is being served (Chapter 5/11), and that route:cache succeeded without complaint — every route across the whole application has pointed at a Controller method since Chapter 3, specifically so this moment would be uneventful.

Chapter Attribution

StepChapter(s) Applied
1. Visiting the deployment11 — Deployment
2. Logging in9 — Admin Authentication
3. Building the five-level chain2 — URL & Content Model, 6 — Eloquent ORM, 10 — Admin CRUD
4. Viewing the page3 — Routing, 4 — Views: Blade, 5 — Styling
5. Editing the title8 — Dynamic Content & Forms, 9 — Admin Authentication
6. Reorganizing2 — deferred cascade cost, 10 — movePage()
7. Delete refusal2 — restrictOnDelete(), 10 — Admin CRUD
8. Kanji migration7 — Rendering Content & the Kanji Edge Case
9. N+1 sanity check6 — Eloquent ORM
10. Deploy health check3 — Routing, 5 — Styling, 11 — Deployment

Honest Scope Note

What this course deliberately doesn't cover
  • No multi-admin roles or permission levels — a single legacy admin credential only
  • No revision history or audit log of content edits
  • No media/image upload handling
  • No automated tests or CI pipeline
  • No internationalization beyond the kanji character-storage edge case itself
  • No rate limiting or lockout policy on the login form
The course's own throughline, closed
Every chapter framed itself against "modernize in place" — reusing the live site's own PHP/Apache stack rather than replacing it outright. That framing pays off concretely in this capstone: Chapter 9's login needed no hasher configuration, and Chapter 11's deployment needed no new reverse-proxy layer, both because the legacy stack Laravel is evolving was already compatible with what Laravel defaults to.

Hands-On Exercises

Exercise 1

Trace Step 6's reorganization through the code: what does movePage() update first, and what does updateDescendantPaths() update afterward, and why does the order matter?

📄 View solution
Exercise 2

Explain what changed between Step 5 in this capstone and the identical-looking action back in Chapter 8, and name the exact line of code responsible for the difference.

📄 View solution
Exercise 3

Name two separate points in this capstone where the course's own "modernize in place" framing paid off concretely, and explain what each one avoided having to do.

📄 View solution

Course Complete

  • 12/12 chapters — Website Rebuild with Laravel is now complete
  • Standout wins named honestly throughout — zero-config bcrypt matching (Ch9), Apache reuse (Ch11), real PHP inside Blade via @php (Ch4)
  • Real limits named honestly too — no free admin panel (Ch10), an extra Controller-class layer Django didn't require (Ch8)
  • Third sibling in the Website Rebuild series — alongside Website Rebuild with Next.js and Website Rebuild with Django
  • Outstanding: Website Rebuild with Ruby on Rails remains the last unbuilt variant