1. The Capstone Brief
The final project: a Laravel API backend that a WordPress site consumes and integrates with. This specific shape is deliberate — it's the one pairing in this course that forces every module to genuinely interact, rather than sitting as four separate, disconnected skills:
- Module 1 (PHP & MySQL) — the fundamentals underneath every line of code you'll write in both halves
- Module 2 (Laravel) — the API backend: models, migrations, auth, Policies, a versioned REST API, tests
- Module 3 (WordPress) — a real site (a custom theme and/or plugin) that calls your API and renders its data
- Module 4 (this module) — security, caching, testing/CI and deployment, applied to the whole system, not a toy example
Concretely: build a Laravel API for a domain of your choosing, then build a
WordPress plugin or theme feature that fetches from it (via wp_remote_get()
— WordPress's built-in HTTP client, the WordPress-side equivalent of the
Http:: facade or curl used elsewhere in this course) and
displays it inside a real WordPress site.
2. Choosing Your Project
Three project shapes that fit this brief well — pick one, or adapt your own idea to this same pattern:
Laravel API: companies, job listings, applications (with file upload résumés)
WordPress: a "Careers" page on a company site pulling live listings from the API,
with an application form posting back to it
Laravel API: events, ticket types, bookings, capacity tracking
WordPress: an events calendar block/shortcode showing upcoming events from the API,
with a booking form and confirmation email (queued, per Week 16)
Laravel API: a proper backend for managing projects, inquiries & client accounts
(replacing the raw PHP/$wpdb version from Weeks 20-21 with a real Laravel API)
WordPress: keeps the Weeks 17-24 theme/plugin, but now calls the Laravel API for data
instead of $wpdb directly -- a genuine architectural upgrade of earlier work
Option C is worth calling out specifically: rebuilding the Portfolio Manager's data layer as a proper external Laravel API, rather than starting from a blank project, is a legitimate and arguably stronger capstone — it demonstrates deliberately replacing an earlier, simpler architecture with a better one, exactly the kind of judgment call real engineering work requires.
3. Architecting the System
Before writing code, answer these explicitly — treat this as a real design document, not a formality:
- Data ownership — which system is the source of truth for which data? (Typically: Laravel owns the domain data; WordPress owns content/presentation.)
- Authentication — how does WordPress authenticate to the Laravel API? (A server-side API key or Sanctum token, stored in WordPress options — never exposed client-side.)
- Caching — how often does WordPress need fresh data from the API? (An hourly transient cache, per Week 26's caching discipline, is often enough and avoids hitting the API on every page load.)
- Failure handling — what does the WordPress site show if the Laravel API is down or slow?
[ WordPress site ] --HTTP, server-side, with an API key--> [ Laravel API ]
↑ ↓
[ visitors ] [ MySQL database ]
↓
[ WordPress admin -- content, theme, plugin config ]
Writing this down before coding is the same discipline as Week 6's schema design before any SQL — architecture decided under pressure, mid-implementation, tends to be worse than architecture decided deliberately, up front, with the full picture in view.
4. Building the API
Apply every relevant Module 2 skill, deliberately, not by copying an old project:
✓ Migrations for every table, with correct foreign keys & indexes (Weeks 6, 11, 26)
✓ Eloquent models with $fillable, relationships & scopes (Weeks 11-12)
✓ Sanctum-secured, versioned v1 API routes (Weeks 13, 15)
✓ API Resources shaping every response (Week 15)
✓ Form Requests for validation on every write endpoint (Week 10, 14)
✓ Policies enforcing ownership/permissions on every relevant action (Week 13)
✓ A queued job for at least one genuinely slow operation -- an email, a report (Week 16)
<?php
// routes/api.php
Route::prefix('v1')->group(function () {
Route::get('/jobs', [JobController::class, 'index']); // public -- no auth needed
Route::get('/jobs/{job}', [JobController::class, 'show']); // public
Route::middleware('auth:sanctum')->group(function () {
Route::post('/jobs', [JobController::class, 'store']); // companies posting jobs
Route::post('/applications', [ApplicationController::class, 'store']);
});
});
Notice the mix: some endpoints are genuinely public (a WordPress careers page needs to read job listings without authenticating), while write operations require a token. Designing this split deliberately — rather than making everything either fully open or fully locked down — is itself a real architectural decision worth making consciously.
5. Testing It Properly
Apply Week 15 and Week 27's testing discipline to this project specifically — not retroactively, but as you build each endpoint:
<?php
test('anyone can list published jobs without authenticating', function () {
Job::factory()->count(3)->published()->create();
$this->getJson('/api/v1/jobs')->assertOk()->assertJsonCount(3);
});
test('draft jobs are not visible in the public listing', function () {
Job::factory()->draft()->create();
$this->getJson('/api/v1/jobs')->assertOk()->assertJsonCount(0);
});
test('posting a job requires authentication', function () {
$this->postJson('/api/v1/jobs', ['title' => 'Backend Engineer'])->assertUnauthorized();
});
test('a company can only edit their own job listings', function () {
$companyA = User::factory()->create();
$job = Job::factory()->for($companyA)->create();
$companyB = User::factory()->create();
$this->actingAs($companyB, 'sanctum')
->putJson("/api/v1/jobs/{$job->id}", ['title' => 'Hacked'])
->assertForbidden();
});
That last test is the same shape as Week 15's ownership test, applied to this specific domain — it's the single most important test in a system like this, since it's the automated proof your Policy from the checklist above actually works, not just something that looked right once during manual testing.
6. This Week's Milestone
Design & build the Laravel API half
Everything in this week's checklist, working and tested, ready for Week 31's WordPress integration.
Requirements:
- Choose your project (one of the three options above, or your own idea fitting the same pattern) and write a one-page architecture doc answering the four questions from Section 3.
- Build the full Laravel API: migrations, models with relationships, Sanctum auth, versioned routes, API Resources, Form Requests and Policies.
- Add at least one queued job for a genuinely slow operation relevant to your domain.
- Write a Pest feature test suite covering: public read access, authenticated write access, validation failures, and an explicit ownership/authorization test per the example above.
- Run
php artisan testandphpstan analyse(Week 27) and confirm both pass cleanly.
Resist the urge to start on the WordPress side before this half is genuinely solid — a wobbly, undertested API makes Week 31's integration work much harder to debug, since you won't be able to tell whether a problem is on the API side or the WordPress side.
7. Knowledge Check
Four quick questions. Expand each to check your answer.
Q1
Why does the capstone brief specifically pair a Laravel API with a WordPress integration, rather than just a standalone Laravel app?
Why does the capstone brief specifically pair a Laravel API with a WordPress integration, rather than just a standalone Laravel app?
It's the one project shape that genuinely forces every module of this course to interact rather than remain four isolated skills demonstrated separately. A standalone Laravel app would only exercise Module 2; pairing it with a real WordPress integration forces decisions about authentication between two systems, caching, failure handling, and applying Module 1's fundamentals underneath both halves.
Q2
Why does the architecture section recommend deciding data ownership and authentication strategy before writing any code?
Why does the architecture section recommend deciding data ownership and authentication strategy before writing any code?
The same reasoning as Week 6's "design the schema before writing any PHP" — decisions made deliberately, with the full picture in view, tend to be sounder than decisions made reactively mid-implementation, under the pressure of already-written code that's awkward to change. Settling data ownership and auth strategy up front avoids discovering a fundamental architectural mismatch halfway through Week 31's integration work.
Q3
In the example route file, why are GET /jobs and GET /jobs/{job} left outside the auth:sanctum middleware group while POST routes are inside it?
In the example route file, why are GET /jobs and GET /jobs/{job} left outside the auth:sanctum middleware group while POST routes are inside it?
A WordPress careers page needs to display job listings to anonymous site visitors, who have no Laravel account or Sanctum token at all — those reads must stay public. Creating a job listing or submitting an application is a write action tied to a specific, identifiable actor (a company, an applicant), which is exactly the kind of action that needs authentication. Mixing public reads with authenticated writes in the same API is a deliberate, common design choice, not an inconsistency.
Q4
Why is the "a company can only edit their own job listings" test singled out as the single most important test in this project?
Why is the "a company can only edit their own job listings" test singled out as the single most important test in this project?
It's the automated proof that the exact class of bug flagged repeatedly throughout this course — authentication without authorization, first raised in Week 8 — genuinely doesn't exist in this codebase. A missing or broken ownership check here would let any authenticated user tamper with any other user's data, which is a far more serious failure mode than a cosmetic bug, and one that's easy to introduce silently during a refactor without a test like this to catch it.