- Director of Education @BitterBrains
- Vue.js Educator
- Full Stack developer
ostafa
The Laravel Backends for Vue.js 3 Course
I just want to:
But I don’t want to deal with creating APIs in Laravel
I just want to:
First-party JavaScript and Laravel Library
class UsersController
{
public function index()
{
$users = User::active()->orderByName()->get(['id', 'name', 'email']);
return Inertia::render('Users', [ 'users' => $users ]);
}
}
<!-- Pages/Users.vue -->
<script setup>
defineProps({ users: Array })
</script>
<template>
<div v-for="user in users" :key="user.id">
<p> {{ user.name }} </p>
</div>
</template>
Build single-page apps,
without
building an API.
https://vueschoo.io
https://vueschoo.io
🌐
Server
Request
https://vueschoo.io
🌐
Server
HTML
Request
Response
https://vueschoo.io
🌐
Server
Request
<html>
<head>
<title>VueSchool</title>
<link href="/css/app.css" rel="stylesheet">
<script src="/js/app.js" defer></script>
</head>
<body>
<div
id="app"
data-page="{'component':'Home','props':{'greet': 'true'}">
</div>
</body>
</html>
The returned HTML contains a root div with data-page
attribute
HTML
Request
Response
https://vueschoo.io
🌐
Server
<div
id="app"
data-page='{"component":"Home","props":{"greet": "true"}}'
></div>
HTML
Client
// Home.vue
<script setup>
defineProps(['greet'])
</script>
<template>
<h1 v-if="greet"> Welcome to VueSchool </h1>
</template>
Welcome To VueSchool
Inertia
Request
Response
https://vueschoo.io
Courses
👆
https://vueschoo.io
Courses
👆
🌐
Server
XHR Request
X-Inertia: true
Headers
https://vueschoo.io
Courses
👆
🌐
Server
XHR Request
Inertia
X-Inertia: true
Headers
https://vueschoo.io
Courses
👆
🌐
Server
XHR Request
JSON Response
JSON
X-Inertia: true
Headers
https://vueschoo.io
Courses
👆
🌐
Server
JSON
RESPONSE
HTTP/1.1 200 OK
Content-Type: application/json
Vary: Accept
X-Inertia: true
{
"component": "Courses",
"props": {
"coursesList": [{}]
},
"url": "/courses",
"version": "version_number"
}
The returned JSON contains an encoded page object.
JSON Response
X-Inertia: true
Headers
XHR Request
https://vueschoo.io
🌐
Server
JSON
{
"component": "Courses",
"props": {
"coursesList": [{}]
},
"url": "/courses",
"version": "version_number"
}
// Courses.vue
<script setup>
defineProps(['courses'])
</script>
<template>
<h1> Courses </h1>
<ul> <li v-for="course in courses"> course.name </li> </ul>
</template>
Inertia
Courses
XHR Request
JSON Response
X-Inertia: true
Headers
<a href="/courses">courses</a>
Courses
👆
https://vueschoo.io
<router-link to="/courses">courses</router-link>
import { Link } from '@inertiajs/vue3'
<Link href="/courses">Courses</Link>
Connects the Vue front-end with the Laravel backend
You get to use Vue components with Laravel server-side routing
Smooth sailing for Laravel developers who uses Vue
No need for Laravel APIs
and Vue developers who uses Laravel
No need for Vue Router to handle client-side routing
Route::get('/courses', function () {
return Inertia::render('Courses', [
'canView' => Route::has('subscribed'),
'coursesData' => data
]);
})->name('courses');
You can create Laravel web routes and use Inertia::render
to render the corresponding page
This will render the Courses.vue
component
from the ~/resources/js/Pages/
directory
Route::get('/courses', function () {
return Inertia::render('Courses', [
'canView' => Route::has('subscribed'),
'coursesData' => data
]);
})->name('courses');
You get to keep your server-side Laravel authentication system 😎
Inertia supports Server-Side Rendering
The returned HTML contains the actual content
<!DOCTYPE html>
<html>
<head> .. </head>
<body>
<main> Actual content .. </main>
</body>
</html>
use Inertia\Inertia;
// Synchronously...
Inertia::share('appName', config('app.name'));
The Inertia::share
feature allows for global data sharing.
This means that the specified data will be included in every request's response.
use Inertia\Inertia;
use App\Models\Cart;
// Lazy...
Inertia::share('shoppingCart', fn () => auth()->user()
? Cart::where('user_id', auth()->user()->id)->get()
: null
);
Shared data will be available in usePage().props
// useShoppingCart() composable
import { computed } from "vue";
import { usePage } from "@inertiajs/vue3";
export function useShoppingCart() {
const page = usePage();
return computed(() => page.props.shoppingCart);
}
<script setup>
import { useShoppingCart } from "../Composables/useShoppingCart";
const shoppingCart = useShoppingCart();
</script>
<template>
<ul>
<li v-for="cartItem in shoppingCart.items">{{ cartItem.name }}</li>
</ul>
</template>
Inertia provides helper component for the page meta
<Head>
<title>VueSchool</title>
<meta name="description" content="Awesome learning experience">
</Head>
<Link href="/logout"
method="post"
as="button"
type="button"
>
Logout
</Link>
// Renders as...
<button type="button">Logout</button>
Can be displayed as any HTML element using the as attribute
<Link href="/logout"
method="post"
as="button"
type="button"
>
Logout
</Link>
// Renders as...
<button type="button">Logout</button>
Can send POST/PUT/PATCH/DELETE
requests using the method attribute
<Link href="/endpoint"
method="post"
:data="{ foo: bar }"
>
Save
</Link>
When making POST
or PUT
requests,
you can add additional data to the request using the data attribute.
<Link href="/endpoint"
method="post"
:data="{ foo: bar }"
:headers="{ foo: bar }"
>
Save
</Link>
The headers attribute allows you to add custom headers to an Inertia link
<script setup>
import { useForm } from '@inertiajs/vue3'
const form = useForm({
email: null,
})
</script>
<template>
<form @submit.prevent="form.post('/newsletter')">
<input type="text" v-model="form.email">
<p v-if="form.errors.email">{{ form.errors.email }}</p>
<button type="submit" :disabled="form.processing">Subscribe</button>
</form>
</template>
Inertia includes a form helper designed to help reduce the amount of boilerplate code needed for handling typical form submissions.
<script setup>
import { useForm } from '@inertiajs/vue3'
const form = useForm({
email: null,
})
</script>
<template>
<form @submit.prevent="form.post('/newsletter')">
<input type="text" v-model="form.email">
<p v-if="form.errors.email">{{ form.errors.email }}</p>
<button type="submit" :disabled="form.processing">Subscribe</button>
</form>
</template>
It supports GET, POST, PUT, PATCH, and DELETE methods for
handling various request types.
If there are form validation errors, they are available via the errors property.
<script setup>
import { useForm } from '@inertiajs/vue3'
const form = useForm({
email: null,
})
</script>
<template>
<form @submit.prevent="form.post('/newsletter')">
<input type="text" v-model="form.email">
<p v-if="form.errors.email">{{ form.errors.email }}</p>
<button type="submit" :disabled="form.processing">Subscribe</button>
</form>
</template>
It can track the form's processing state
<script setup>
import { useForm } from '@inertiajs/vue3'
const form = useForm({
email: null,
})
</script>
<template>
<form @submit.prevent="form.post('/newsletter')">
<input type="text" v-model="form.email">
<p v-if="form.errors.email">{{ form.errors.email }}</p>
<button type="submit" :disabled="form.processing">Subscribe</button>
</form>
</template>
<script setup>
import { useForm } from '@inertiajs/vue3'
const form = useForm('uniqueId', {
email: null,
})
</script>
By providing a unique ID to the useForm
helper, the form data and errors will automatically be remembered if the user navigates away
When using Inertia with Laravel, you get CSRF protection out of the box.
No additional configuration is required
<script setup>
import { useForm } from '@inertiajs/vue3'
const form = useForm({
avatar: null,
})
</script>
<template>
<form @submit.prevent="form.post('/avatars')">
<input type="file" @input="form.avatar = $event.target.files[0]" />
<progress v-if="form.progress" :value="form.progress.percentage" max="100">
{{ form.progress.percentage }}%
</progress>
<button type="submit">Submit</button>
</form>
</template>
When making Inertia requests that include files,
Inertia will automatically convert the request data into a FormData
object.
<script setup>
import { useForm } from '@inertiajs/vue3'
const form = useForm({
avatar: null,
})
</script>
<template>
<form @submit.prevent="form.post('/avatars')">
<input type="file" @input="form.avatar = $event.target.files[0]" />
<progress v-if="form.progress" :value="form.progress.percentage" max="100">
{{ form.progress.percentage }}%
</progress>
<button type="submit">Submit</button>
</form>
</template>
The form helper provides easy access to the current upload progress
import { router } from '@inertiajs/vue3'
const filterCourses = () => {
// fetch and filter
router.reload({ only: ['courses'] })
}
When re-requesting the same page,
you can selectively reload specific properties without altering surrounding data.
import { Link } from '@inertiajs/vue3'
<Link href="/courses?free=true" :only="['courses']">Show Free Courses</Link>
The 'only' option is also available as a prop to the <Link>
component.
Inertia, Vue, and Laravel work smoothly together to build dynamic, server-driven single-page apps.
It still got so many more features to explore
inertiajs.com
Polling: Auto-refresh data at intervals for real-time updates.
Prefetching: Fetch data in advance for instant responses.
Deferred Props: Load essential data first for faster UI.
Infinite Scrolling: Load content as users scroll.
Lazy Loading: Load data only when needed.
But, it's not the only way to do it
// UserProfile.vue
<php>
// Define a prop in PHP
$name = prop(Auth::user()->name);
</php>
<template>
<!-- Use it in Vue! -->
Hello {{ name }}!
</template>
Crazy stuff are waiting ahead!
// UserProfile.vue
<php>
// Define a prop in PHP
$name = prop(Auth::user()->name);
</php>
<template>
<!-- Use it in Vue! -->
Hello {{ name }}!
</template>
Laravel Fusion
By: Aaron Francis
github.com/fusion-php/fusion
But before I go!
Mastering Nuxt: 2025 Edition
masteringnuxt.com/2025
Invest in
Invest in BitterBrains
bitterbrains.com
republic.com/bitterbrains
I'll post out the slides after this
@Moose_Said
@mosaid.bsky.social
🦋
Go code awesome things!
(with Vue and Laravel 😉)