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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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
| Step | Chapter(s) Applied |
|---|---|
| 1. Visiting the deployment | 11 — Deployment |
| 2. Logging in | 9 — Admin Authentication |
| 3. Building the five-level chain | 2 — URL & Content Model, 6 — Eloquent ORM, 10 — Admin CRUD |
| 4. Viewing the page | 3 — Routing, 4 — Views: Blade, 5 — Styling |
| 5. Editing the title | 8 — Dynamic Content & Forms, 9 — Admin Authentication |
| 6. Reorganizing | 2 — deferred cascade cost, 10 — movePage() |
| 7. Delete refusal | 2 — restrictOnDelete(), 10 — Admin CRUD |
| 8. Kanji migration | 7 — Rendering Content & the Kanji Edge Case |
| 9. N+1 sanity check | 6 — Eloquent ORM |
| 10. Deploy health check | 3 — Routing, 5 — Styling, 11 — Deployment |
Honest Scope Note
- 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
Hands-On Exercises
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?
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 solutionName 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 solutionCourse 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