15: Full-Stack - Inertia.js (React/Vue + Laravel)
Full-Stack: Inertia.js (React/Vue + Laravel)
Section titled “Full-Stack: Inertia.js (React/Vue + Laravel)”Overview
Section titled “Overview”Inertia.js bridges the gap between Laravel’s backend excellence and React/Vue’s frontend power. If you’re a TypeScript developer who wants to use Laravel, Inertia lets you build SPAs without building an API. It’s like Next.js, but with Laravel as the backend.
Learning Objectives
Section titled “Learning Objectives”By the end of this chapter, you’ll be able to:
- ✅ Set up Inertia.js with Laravel
- ✅ Build SPAs without building APIs
- ✅ Use React/Vue with Laravel seamlessly
- ✅ Handle routing and navigation
- ✅ Share data between frontend and backend
- ✅ Implement forms with validation
- ✅ Use TypeScript with Inertia
Code Examples
Section titled “Code Examples”📁 View Code Examples on GitHub
This chapter includes Inertia.js setup and examples - comprehensive guide for building full-stack applications with Laravel + React/Vue.
Get started with Inertia.js:
# View the complete setup guide on GitHub# The README includes:# - Complete installation steps# - Basic example with React# - Form handling# - TypeScript configuration# - Architecture diagrams
# Quick start:composer create-project laravel/laravel inertia-appcd inertia-appcomposer require inertiajs/inertia-laravelnpm install @inertiajs/react react react-domWhat is Inertia.js?
Section titled “What is Inertia.js?”Problem:
- Traditional SPAs: Build separate frontend (React) + backend API (Laravel)
- SSR Frameworks: Next.js/Nuxt great for Node.js, but what about Laravel?
Solution: Inertia.js
- Write Laravel controllers (no API endpoints)
- Use React/Vue components for UI
- No client-side routing needed
- Server-side routing with SPA feel
- TypeScript-first
Architecture Comparison
Section titled “Architecture Comparison”Next.js (with API Routes):
React Frontend → API Routes → DatabaseInertia.js:
React/Vue Frontend ← Inertia Protocol → Laravel Backend → DatabaseTraditional SPA:
React Frontend → REST API (Laravel) → Database(Two separate apps)Inertia Benefits:
- ✅ Single codebase
- ✅ No API to build/maintain
- ✅ Server-side routing
- ✅ Use Laravel auth, validation, etc.
- ✅ TypeScript support
- ✅ SSR available
Installation
Section titled “Installation”Create Laravel Project with Inertia
Section titled “Create Laravel Project with Inertia”# Create Laravel projectcomposer create-project laravel/laravel my-appcd my-app
# Install Inertia server-sidecomposer require inertiajs/inertia-laravel
# Install frontend dependenciesnpm install @inertiajs/react react react-dom# Or for Vue:# npm install @inertiajs/vue3 vue@next
# Install Vite pluginnpm install --save-dev @vitejs/plugin-react
# Set up Inertia middlewarephp artisan inertia:middlewareapp/Http/Kernel.php:
'web' => [ // ... \App\Http\Middleware\HandleInertiaRequests::class,],resources/js/app.jsx:
import './bootstrap';import { createRoot } from 'react-dom/client';import { createInertiaApp } from '@inertiajs/react';
createInertiaApp({ resolve: name => { const pages = import.meta.glob('./Pages/**/*.jsx', { eager: true }); return pages[`./Pages/${name}.jsx`]; }, setup({ el, App, props }) { createRoot(el).render(<App {...props} />); },});vite.config.js:
import { defineConfig } from 'vite';import laravel from 'laravel-vite-plugin';import react from '@vitejs/plugin-react';
export default defineConfig({ plugins: [ laravel(['resources/js/app.jsx']), react(), ],});Basic Example
Section titled “Basic Example”Laravel Controller
Section titled “Laravel Controller”<?phpnamespace App\Http\Controllers;
use App\Models\User;use Inertia\Inertia;
class UserController extends Controller { public function index() { return Inertia::render('Users/Index', [ 'users' => User::all() ]); }
public function show(User $user) { return Inertia::render('Users/Show', [ 'user' => $user ]); }}React Component
Section titled “React Component”import { Link } from '@inertiajs/react';
interface User { id: number; name: string; email: string;}
interface Props { users: User[];}
export default function UsersIndex({ users }: Props) { return ( <div> <h1>Users</h1> <ul> {users.map(user => ( <li key={user.id}> <Link href={`/users/${user.id}`}> {user.name} </Link> </li> ))} </ul> </div> );}No API needed! Laravel passes data directly to React.
Routing
Section titled “Routing”Laravel Routes
Section titled “Laravel Routes”<?phpuse App\Http\Controllers\UserController;
Route::get('/users', [UserController::class, 'index']);Route::get('/users/{user}', [UserController::class, 'show']);No API routes needed! Inertia handles communication.
Navigation (React)
Section titled “Navigation (React)”import { Link, router } from '@inertiajs/react';
// Link component (like Next.js Link)<Link href="/users">Users</Link>
// Programmatic navigationrouter.visit('/users');
// With methodrouter.post('/users', { name: 'Alice', email: 'alice@example.com' });Forms and Validation
Section titled “Forms and Validation”Traditional Next.js Form
Section titled “Traditional Next.js Form”// Next.js approachconst handleSubmit = async (e) => { e.preventDefault(); const response = await fetch('/api/users', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(formData), }); const data = await response.json();};Inertia Form (React)
Section titled “Inertia Form (React)”import { useForm } from '@inertiajs/react';
export default function CreateUser() { const { data, setData, post, processing, errors } = useForm({ name: '', email: '', });
const submit = (e: React.FormEvent) => { e.preventDefault(); post('/users'); };
return ( <form onSubmit={submit}> <input value={data.name} onChange={e => setData('name', e.target.value)} /> {errors.name && <div>{errors.name}</div>}
<input value={data.email} onChange={e => setData('email', e.target.value)} /> {errors.email && <div>{errors.email}</div>}
<button type="submit" disabled={processing}> Create User </button> </form> );}Laravel Controller
Section titled “Laravel Controller”<?phpclass UserController extends Controller { public function store(Request $request) { $validated = $request->validate([ 'name' => 'required|string|max:255', 'email' => 'required|email|unique:users', ]);
$user = User::create($validated);
return redirect()->route('users.show', $user); }}Validation errors automatically sent back to React component!
Shared Data
Section titled “Shared Data”Middleware (Share Data Globally)
Section titled “Middleware (Share Data Globally)”<?phpclass HandleInertiaRequests extends Middleware { public function share(Request $request): array { return array_merge(parent::share($request), [ 'auth' => [ 'user' => $request->user(), ], 'flash' => [ 'success' => $request->session()->get('success'), 'error' => $request->session()->get('error'), ], ]); }}Access in React
Section titled “Access in React”import { usePage } from '@inertiajs/react';
export default function Navigation() { const { auth } = usePage<{ auth: { user: User | null } }>().props;
return ( <nav> {auth.user ? ( <span>Hello, {auth.user.name}</span> ) : ( <a href="/login">Login</a> )} </nav> );}TypeScript Support
Section titled “TypeScript Support”Generate TypeScript Types
Section titled “Generate TypeScript Types”Install Ziggy (Laravel route helper):
composer require tightenco/ziggyGenerate types:
php artisan ziggy:generateUse in TypeScript:
import { route } from 'ziggy-js';
// Type-safe routing!<Link href={route('users.show', user.id)}>View User</Link>Shared Types
Section titled “Shared Types”export interface User { id: number; name: string; email: string; created_at: string;}
export interface Post { id: number; title: string; body: string; user: User;}
export interface PageProps { auth: { user: User | null; }; flash: { success?: string; error?: string; };}Complete CRUD Example
Section titled “Complete CRUD Example”Laravel Controller
Section titled “Laravel Controller”<?phpnamespace App\Http\Controllers;
use App\Models\Post;use Illuminate\Http\Request;use Inertia\Inertia;
class PostController extends Controller { public function index() { return Inertia::render('Posts/Index', [ 'posts' => Post::with('user')->latest()->get() ]); }
public function create() { return Inertia::render('Posts/Create'); }
public function store(Request $request) { $validated = $request->validate([ 'title' => 'required|max:255', 'body' => 'required', ]);
$request->user()->posts()->create($validated);
return redirect()->route('posts.index') ->with('success', 'Post created!'); }
public function edit(Post $post) { return Inertia::render('Posts/Edit', [ 'post' => $post ]); }
public function update(Request $request, Post $post) { $validated = $request->validate([ 'title' => 'required|max:255', 'body' => 'required', ]);
$post->update($validated);
return redirect()->route('posts.index') ->with('success', 'Post updated!'); }
public function destroy(Post $post) { $post->delete();
return redirect()->route('posts.index') ->with('success', 'Post deleted!'); }}React Components
Section titled “React Components”import { Link } from '@inertiajs/react';import Layout from '@/Layouts/Layout';
interface Post { id: number; title: string; body: string; user: { name: string };}
export default function PostsIndex({ posts }: { posts: Post[] }) { return ( <Layout> <div className="flex justify-between items-center mb-6"> <h1 className="text-2xl font-bold">Posts</h1> <Link href="/posts/create" className="bg-blue-500 text-white px-4 py-2 rounded" > Create Post </Link> </div>
<div className="space-y-4"> {posts.map(post => ( <div key={post.id} className="border p-4 rounded"> <h2 className="text-xl font-semibold">{post.title}</h2> <p className="text-gray-600">By {post.user.name}</p> <p className="mt-2">{post.body}</p> <div className="mt-4 space-x-2"> <Link href={`/posts/${post.id}/edit`} className="text-blue-500" > Edit </Link> <Link href={`/posts/${post.id}`} method="delete" as="button" className="text-red-500" > Delete </Link> </div> </div> ))} </div> </Layout> );}import { useForm } from '@inertiajs/react';import Layout from '@/Layouts/Layout';
export default function CreatePost() { const { data, setData, post, processing, errors } = useForm({ title: '', body: '', });
const submit = (e: React.FormEvent) => { e.preventDefault(); post('/posts'); };
return ( <Layout> <h1 className="text-2xl font-bold mb-6">Create Post</h1>
<form onSubmit={submit} className="space-y-4"> <div> <label className="block mb-1">Title</label> <input type="text" value={data.title} onChange={e => setData('title', e.target.value)} className="w-full border px-3 py-2 rounded" /> {errors.title && ( <div className="text-red-500 text-sm mt-1">{errors.title}</div> )} </div>
<div> <label className="block mb-1">Body</label> <textarea value={data.body} onChange={e => setData('body', e.target.value)} className="w-full border px-3 py-2 rounded" rows={6} /> {errors.body && ( <div className="text-red-500 text-sm mt-1">{errors.body}</div> )} </div>
<button type="submit" disabled={processing} className="bg-blue-500 text-white px-4 py-2 rounded" > Create Post </button> </form> </Layout> );}Authentication
Section titled “Authentication”Laravel Breeze with Inertia provides complete auth:
composer require laravel/breeze --devphp artisan breeze:install reactnpm install && npm run devphp artisan migrateIncludes:
- Login/Register pages (React)
- Password reset
- Email verification
- Profile management
- Middleware protection
// Use in componentsimport { usePage } from '@inertiajs/react';
const { auth } = usePage().props;
if (auth.user) { // User is logged in}Server-Side Rendering (SSR)
Section titled “Server-Side Rendering (SSR)”Enable SSR for better SEO and performance:
npm install @inertiajs/react @inertiajs/serverresources/js/ssr.jsx:
import { createServer } from '@inertiajs/server';import { createInertiaApp } from '@inertiajs/react';import ReactDOMServer from 'react-dom/server';
createServer(page => createInertiaApp({ page, render: ReactDOMServer.renderToString, resolve: name => { const pages = import.meta.glob('./Pages/**/*.jsx', { eager: true }); return pages[`./Pages/${name}.jsx`]; }, setup: ({ App, props }) => <App {...props} />, }),);Comparison with Other Stacks
Section titled “Comparison with Other Stacks”| Feature | Next.js | Remix | Inertia + Laravel |
|---|---|---|---|
| Backend | Node.js | Node.js | PHP/Laravel |
| Frontend | React | React | React/Vue |
| Routing | File-based | File-based | Laravel routes |
| Data Fetching | getServerSideProps | loader | Controller return |
| Forms | Manual | <Form> + action | useForm hook |
| Auth | NextAuth | Custom | Laravel Breeze |
| TypeScript | ✅ | ✅ | ✅ |
| SSR | ✅ | ✅ | ✅ (optional) |
Key Takeaways
Section titled “Key Takeaways”- Inertia bridges Laravel + React/Vue seamlessly - no API layer needed
- No API needed - controllers return props directly to React components
- TypeScript support is first-class with type generation from Laravel models
- Forms and validation are incredibly simple - submit to Laravel routes directly
- Feels like SPA but server-side rendered - best of both architectures
- Best of both worlds - Laravel backend power + React/Vue frontend DX
- Perfect for TypeScript devs who want Laravel without building separate API
- Server-side routing with client-side navigation - no route duplication
- Ziggy provides Laravel routes in JavaScript - type-safe route helpers
- Shared data automatically available to all pages (auth user, flash messages, etc.)
- Partial reloads only fetch changed data - efficient like GraphQL without complexity
- SSR support available via Inertia SSR - true server-side rendering for SEO
When to Use Inertia
Section titled “When to Use Inertia”✅ Use Inertia When:
Section titled “✅ Use Inertia When:”- Building full-stack applications
- Want Laravel backend + React/Vue frontend
- Don’t need separate mobile app (use API instead)
- Prefer monolithic architecture
- Want rapid development
❌ Don’t Use Inertia When:
Section titled “❌ Don’t Use Inertia When:”- Need separate API for mobile apps
- Building microservices
- Frontend and backend teams are separate
- Need GraphQL
- Want static site generation
Next Steps
Section titled “Next Steps”Congratulations! 🎉 You’ve completed the entire “PHP for TypeScript Developers” series!
You now have the skills to:
- ✅ Build REST APIs with Laravel
- ✅ Use Eloquent ORM effectively
- ✅ Create full-stack applications with Inertia
- ✅ Apply TypeScript knowledge to PHP
- ✅ Use modern PHP tooling
Continue Learning
Section titled “Continue Learning”- Laravel Documentation: https://laravel.com/docs
- Inertia.js Docs: https://inertiajs.com
- Laracasts: https://laracasts.com (video tutorials)
- Build real projects! The best way to learn
Series Complete! 🚀
Section titled “Series Complete! 🚀”You’ve mastered:
Phase 1: Foundations
- Type systems, syntax, functions, OOP, error handling
Phase 2: Ecosystem
- Package management, testing, quality tools, build process, debugging
Phase 3: Advanced & Frameworks
- Async patterns, REST APIs, Laravel, Eloquent ORM, full-stack development
You’re now a full-stack PHP developer with TypeScript superpowers!
Resources
Section titled “Resources”- Inertia.js Documentation
- Laravel Breeze
- Ziggy (Route Helper)
- Laravel Jetstream - Advanced starter kit
- Inertia.js Pingcrm Demo - Full example app
Thank you for completing this series! 🎉
Questions or feedback? Open an issue on GitHub