1. The Problem Inertia Solves
Week 15's REST API approach means building and maintaining two separate concerns: a versioned API surface (routes, Resources, auth tokens) and a frontend that consumes it, each with its own routing, its own auth handling, its own request/response contracts to keep in sync.
Inertia.js takes a different position: keep Laravel's routing, controllers, session-based auth and validation exactly as built in Weeks 9-14 — but instead of returning a Blade view, a controller returns the name of a Vue or React component plus the data it needs. Inertia handles turning that into a real client-side page render, with no API layer in between at all.
Blade (Week 9): Controller → Blade view → full page reload on every navigation
REST + separate app: Controller → JSON → a fully separate frontend app, own routing
Inertia (this week): Controller → Vue/React component + data → SPA-like nav, one Laravel app
Inertia is the right fit specifically when a team wants a modern, SPA-feeling frontend without paying the cost of a second, fully independent application to build, deploy and keep in sync — a real trade-off against Week 15's approach, not a strict upgrade.
2. Setup
composer require inertiajs/inertia-laravel
php artisan inertia:middleware
npm install @inertiajs/vue3 vue
<?php
->withMiddleware(function ($middleware) {
$middleware->web(append: [
\App\Http\Middleware\HandleInertiaRequests::class,
]);
})
This registers Inertia's middleware alongside every other web
middleware from Week 14 — auth, CSRF protection, and session handling
all continue to work exactly as before. Inertia adds a rendering layer on top;
it doesn't replace the request pipeline underneath.
3. Rendering Pages
A controller returns Inertia::render() instead of view()
— same routing and controller structure from Week 10, different return value:
<?php
use Inertia\Inertia;
class TaskController extends Controller
{
public function index()
{
return Inertia::render('Tasks/Index', [
'tasks' => auth()->user()->tasks()->with('tags')->get(), // eager-loaded, per Week 12
]);
}
}
<script setup>
defineProps({ tasks: Array });
</script>
<template>
<div>
<h1>Tasks</h1>
<ul>
<li v-for="task in tasks" :key="task.id">
{{ task.title }}
<span v-if="task.is_done">(done)</span>
</li>
</ul>
</div>
</template>
'Tasks/Index' resolves to resources/js/Pages/Tasks/Index.vue
by convention. Navigation between Inertia pages happens client-side, without a
full page reload — but every navigation still goes through a real Laravel route
and controller, unlike a traditional SPA's client-only routing.
4. Forms & Validation
This is where Inertia's approach diverges most visibly from Week 15's API pattern — validation errors from a Form Request flow straight through to the frontend, with no manual JSON error-handling required:
<script setup>
import { useForm } from '@inertiajs/vue3';
const form = useForm({ title: '', priority: 'medium' });
function submit() {
form.post('/tasks'); // hits the exact same store() route from Week 10
}
</script>
<template>
<form @submit.prevent="submit">
<input v-model="form.title">
<div v-if="form.errors.title">{{ form.errors.title }}</div>
<button :disabled="form.processing">Create</button>
</form>
</template>
<?php
public function store(StoreTaskRequest $request)
{
auth()->user()->tasks()->create($request->validated());
return redirect()->route('tasks.index');
}
When StoreTaskRequest's validation fails, Inertia automatically
catches the redirect-with-errors response (Week 10's exact mechanism) and
populates form.errors on the client — the same server-side validation
rules from Week 10, now surfacing directly in the Vue component with zero extra
client-side validation logic duplicated.
6. Hands-on Exercise
Convert the task tracker's UI to Inertia + Vue
Replace Blade views with Inertia pages, keeping every controller, Policy and validation rule unchanged.
Requirements:
- Install Inertia + Vue and confirm the middleware is registered.
- Convert
tasks.index,tasks.createandtasks.showfrom Blade views to Inertia-rendered Vue pages, without changing any controller logic beyond swappingview()forInertia::render(). - Build the create-task form with
useForm(), confirming server-side validation errors fromStoreTaskRequest(Week 10) surface correctly in the Vue component. - Share the authenticated user and flash success messages globally via
HandleInertiaRequests, and display the flash message in a shared layout component. - Confirm
TaskPolicy's ownership checks (Week 13) still 403 correctly — authorization logic shouldn't need any changes at all for this migration to work.
If this migration goes well, your controllers, Form Requests, and Policies should need almost no changes — that's the actual point being demonstrated: Inertia is a rendering-layer swap, not a rewrite of the application's underlying logic.
7. Knowledge Check
Four quick questions. Expand each to check your answer.
Q1
What's the fundamental difference between Week 15's REST API approach and this week's Inertia.js approach?
What's the fundamental difference between Week 15's REST API approach and this week's Inertia.js approach?
A REST API creates a genuine boundary between two independent applications — a Laravel backend and a fully separate frontend, each with its own routing, auth handling and deploy lifecycle, communicating only through versioned JSON endpoints. Inertia keeps everything as one Laravel application: the same routes, controllers, session auth and validation, just rendering a JavaScript component instead of a Blade view — there's no separate API surface to design, version or maintain at all.
Q2
Why does TaskController@store need zero changes to work correctly with Inertia's useForm()?
Why does TaskController@store need zero changes to work correctly with Inertia's useForm()?
Because Inertia is built specifically to understand Laravel's existing validation-failure response (the redirect-back-with-errors pattern from Week 10) and translate it automatically into form.errors on the client. The controller's StoreTaskRequest validation logic never had to be redesigned around Inertia — Inertia was designed to work with the pattern Laravel already uses.
Q3
Why should TaskPolicy's ownership checks from Week 13 keep working correctly after migrating to Inertia, with no changes to the Policy itself?
Why should TaskPolicy's ownership checks from Week 13 keep working correctly after migrating to Inertia, with no changes to the Policy itself?
Authorization runs at the controller level ($this->authorize('delete', $task)), entirely independent of what the controller eventually returns. Inertia only changes the response format at the very end of the request — the entire middleware stack, routing, and authorization logic before that point is unaffected, which is exactly the point of Inertia being a rendering-layer change rather than an architectural rewrite.
Q4
When would Week 15's separate REST API still be the better choice over Inertia?
When would Week 15's separate REST API still be the better choice over Inertia?
When more than one genuinely separate client needs the same backend — a mobile app and a web frontend both consuming the same data, for instance, or a third party integrating with your API directly. Inertia's tight coupling between the Laravel app and its one specific frontend is a strength for a single-application team, but it doesn't fit a situation where multiple independent clients need the same underlying data through a stable, versioned contract.