In this tutorial, we will create CRUD app using Laravel Inertia Js with vue 3. For this section we will use Vue 3 SFCs method <script setup> and latest laravel 9 with vite.
Step 1: Install Laravel & Connect Database
Run below command to create laravel protect.
composer create-project laravel/laravel inertia-crud
Now, you have to connect the laravel app to the database, hence open the .env configuration file and add the database credentials as suggested below.
.env
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=database_name
DB_USERNAME=database_user_name
DB_PASSWORD=database_password
Step 2: Install Breeze & Setup Inertia Js Vue 3
Install laravel breeze via composer
composer require laravel/breeze --dev
Next, run below command
php artisan breeze:install
install breeze with vue 3
php artisan breeze:install vue
And final install Dependencies
npm install && npm run dev
Step 3: Create Blog Modal Migration and Controller Route
Run below command to create blog modal, migration and controller
php artisan make:model Blog -mcr
create_blogs_table.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('blogs', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->string('slug')->unique();
$table->text('content');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('blogs');
}
};
App\Models\Blog.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Blog extends Model
{
use HasFactory;
protected $fillable = [
'title',
'slug',
'content'
];
}
web.php
<?php
use App\Http\Controllers\BlogController;
use Illuminate\Foundation\Application;
use Illuminate\Support\Facades\Route;
use Inertia\Inertia;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/
Route::get('/', function () {
return Inertia::render('Welcome', [
'canLogin' => Route::has('login'),
'canRegister' => Route::has('register'),
'laravelVersion' => Application::VERSION,
'phpVersion' => PHP_VERSION,
]);
});
Route::get('/dashboard', function () {
return Inertia::render('Dashboard');
})->middleware(['auth', 'verified'])->name('dashboard');
Route::resource('blogs',BlogController::class);
require __DIR__.'/auth.php';
BlogController.php
<?php
namespace App\Http\Controllers;
use App\Models\Blog;
use Illuminate\Http\Request;
use Inertia\Inertia;
class BlogController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$blogs = Blog::all();
return Inertia::render(
'Blogs/Index',
[
'blogs' => $blogs
]
);
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
return Inertia::render(
'Blogs/Create'
);
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$request->validate([
'title' => 'required|string|max:255',
'slug' => 'required|string|max:255',
'content' => 'required',
]);
Blog::create([
'title' => $request->title,
'slug' => \Str::slug($request->slug),
'content' => $request->content
]);
sleep(1);
return redirect()->route('blogs.index')->with('message', 'Blog Created Successfully');
}
/**
* Display the specified resource.
*
* @param \App\Models\Blog $blog
* @return \Illuminate\Http\Response
*/
public function show(Blog $blog)
{
//
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Blog $blog
* @return \Illuminate\Http\Response
*/
public function edit(Blog $blog)
{
return Inertia::render(
'Blogs/Edit',
[
'blog' => $blog
]
);
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param \App\Models\Blog $blog
* @return \Illuminate\Http\Response
*/
public function update(Request $request, Blog $blog)
{
$request->validate([
'title' => 'required|string|max:255',
'slug' => 'required|string|max:255',
'content' => 'required',
]);
$blog->title = $request->title;
$blog->slug = \Str::slug($request->slug);
$blog->content = $request->content;
$blog->save();
sleep(1);
return redirect()->route('blogs.index')->with('message', 'Blog Updated Successfully');
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Blog $blog
* @return \Illuminate\Http\Response
*/
public function destroy(Blog $blog)
{
$blog->delete();
sleep(1);
return redirect()->route('blogs.index')->with('message', 'Blog Delete Successfully');
}
}
Step 4: Create Blog View File
Next, you need to create Index.vue Create.vue and Edit.vue file and put vue 3 perform crud operation
Blogs/Index.vue
<script setup>
import BreezeAuthenticatedLayout from "@/Layouts/Authenticated.vue";
import { Head } from "@inertiajs/inertia-vue3";
import BreezeButton from "@/Components/Button.vue";
import { Link } from "@inertiajs/inertia-vue3";
import { Inertia } from "@inertiajs/inertia";
import { useForm } from '@inertiajs/inertia-vue3'
const props = defineProps({
blogs: {
type: Object,
default: () => ({}),
},
});
const form = useForm();
function destroy(id) {
if (confirm("Are you sure you want to Delete")) {
form.delete(route('blogs.destroy', id));
}
}
</script>
<template>
<Head title="Blogs" />
<BreezeAuthenticatedLayout>
<template #header>
<h2 class="text-xl font-semibold leading-tight text-gray-800">
Blogs Index
</h2>
</template>
<div class="py-12">
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8">
<div
v-if="$page.props.flash.message"
class="p-4 mb-4 text-sm text-green-700 bg-green-100 rounded-lg dark:bg-green-200 dark:text-green-800"
role="alert"
>
<span class="font-medium">
{{ $page.props.flash.message }}
</span>
</div>
<div class="overflow-hidden bg-white shadow-sm sm:rounded-lg">
<div class="p-6 bg-white border-b border-gray-200">
<div class="mb-2">
<Link :href="route('blogs.create')">
<BreezeButton>Add Blog</BreezeButton></Link
>
</div>
<div
class="relative overflow-x-auto shadow-md sm:rounded-lg"
>
<table
class="w-full text-sm text-left text-gray-500 dark:text-gray-400"
>
<thead
class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400"
>
<tr>
<th scope="col" class="px-6 py-3">#</th>
<th scope="col" class="px-6 py-3">
Title
</th>
<th scope="col" class="px-6 py-3">
Slug
</th>
<th scope="col" class="px-6 py-3">
Edit
</th>
<th scope="col" class="px-6 py-3">
Delete
</th>
</tr>
</thead>
<tbody>
<tr
v-for="blog in blogs"
:key="blog.id"
class="bg-white border-b dark:bg-gray-800 dark:border-gray-700"
>
<th
scope="row"
class="px-6 py-4 font-medium text-gray-900 dark:text-white whitespace-nowrap"
>
{{ blog.id }}
</th>
<th
scope="row"
class="px-6 py-4 font-medium text-gray-900 dark:text-white whitespace-nowrap"
>
{{ blog.title }}
</th>
<td class="px-6 py-4">
{{ blog.slug }}
</td>
<td class="px-6 py-4">
<Link
:href="
route(
'blogs.edit',
blog.id
)
"
class="px-4 py-2 text-white bg-blue-600 rounded-lg" >Edit</Link
>
</td>
<td class="px-6 py-4">
<BreezeButton
class="bg-red-700"
@click="destroy(blog.id)"
>
Delete
</BreezeButton>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</BreezeAuthenticatedLayout>
</template>
Blogs/Create.vue
<script setup>
import BreezeAuthenticatedLayout from "@/Layouts/Authenticated.vue";
import { Head } from "@inertiajs/inertia-vue3";
import BreezeButton from "@/Components/Button.vue";
import { Link } from "@inertiajs/inertia-vue3";
import { useForm } from "@inertiajs/inertia-vue3";
const props = defineProps({
blogs: {
type: Object,
default: () => ({}),
},
});
const form = useForm({
title: '',
slug: '',
content: '',
});
const submit = () => {
form.post(route("blogs.store"));
};
</script>
<template>
<Head title="Blog Create" />
<BreezeAuthenticatedLayout>
<template #header>
<h2 class="text-xl font-semibold leading-tight text-gray-800">
Blog Create
</h2>
</template>
<div class="py-12">
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8">
<div class="overflow-hidden bg-white shadow-sm sm:rounded-lg">
<div class="p-6 bg-white border-b border-gray-200">
<form @submit.prevent="submit">
<div class="mb-6">
<label
for="Title"
class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
>Title</label
>
<input
type="text"
v-model="form.title"
name="title"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
placeholder=""
/>
<div
v-if="form.errors.title"
class="text-sm text-red-600"
>
{{ form.errors.title }}
</div>
</div>
<div class="mb-6">
<label
for="Slug"
class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
>Slug</label
>
<input
type="text"
v-model="form.slug"
name="title"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
placeholder=""
/>
<div
v-if="form.errors.slug"
class="text-sm text-red-600"
>
{{ form.errors.slug }}
</div>
</div>
<div class="mb-6">
<label
for="slug"
class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
>Content</label
>
<textarea
type="text"
v-model="form.content"
name="content"
id=""
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
></textarea>
<div
v-if="form.errors.content"
class="text-sm text-red-600"
>
{{ form.errors.content }}
</div>
</div>
<button
type="submit"
class="text-white bg-blue-700 focus:outline-none font-medium rounded-lg text-sm px-5 py-2.5 "
:disabled="form.processing"
:class="{ 'opacity-25': form.processing }"
>
Submit
</button>
</form>
</div>
</div>
</div>
</div>
</BreezeAuthenticatedLayout>
</template>
Blogs/Edit.vue
<script setup>
import BreezeAuthenticatedLayout from "@/Layouts/Authenticated.vue";
import { Head } from "@inertiajs/inertia-vue3";
import BreezeButton from "@/Components/Button.vue";
import { Link } from "@inertiajs/inertia-vue3";
import { useForm } from "@inertiajs/inertia-vue3";
const props = defineProps({
blog: {
type: Object,
default: () => ({}),
},
});
const form = useForm({
id: props.blog.id,
title: props.blog.title,
slug: props.blog.slug,
content: props.blog.content,
});
const submit = () => {
form.put(route("blogs.update", props.blog.id));
};
</script>
<template>
<Head title="Blog Edit" />
<BreezeAuthenticatedLayout>
<template #header>
<h2 class="text-xl font-semibold leading-tight text-gray-800">
Blog Edit
</h2>
</template>
<div class="py-12">
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8">
<div class="overflow-hidden bg-white shadow-sm sm:rounded-lg">
<div class="p-6 bg-white border-b border-gray-200">
<form @submit.prevent="submit">
<div class="mb-6">
<label
for="Title"
class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
>Title</label
>
<input
type="text"
v-model="form.title"
name="title"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
placeholder=""
/>
<div
v-if="form.errors.title"
class="text-sm text-red-600"
>
{{ form.errors.title }}
</div>
</div>
<div class="mb-6">
<label
for="Slug"
class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
>Slug</label
>
<input
type="text"
v-model="form.slug"
name="title"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
placeholder=""
/>
<div
v-if="form.errors.slug"
class="text-sm text-red-600"
>
{{ form.errors.slug }}
</div>
</div>
<div class="mb-6">
<label
for="slug"
class="block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300"
>Content</label
>
<textarea
type="text"
v-model="form.content"
name="content"
id=""
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
></textarea>
<div
v-if="form.errors.content"
class="text-sm text-red-600"
>
{{ form.errors.content }}
</div>
</div>
<button
type="submit"
class="text-white bg-blue-700 focus:outline-none font-medium rounded-lg text-sm px-5 py-2.5 "
:disabled="form.processing"
:class="{ 'opacity-25': form.processing }"
>
Submit
</button>
</form>
</div>
</div>
</div>
</div>
</BreezeAuthenticatedLayout>
</template>
Step 5: Run Application Server
run the serve
php artisan serve
run vite server
npm run dev
Read Also
Laravel 9 Inertia Vue 3 Integrating Dashboard Example
Laravel 9 Inertia Vue 3 DataTables Example
Laravel 9 Inertia Vue 3 File Upload Example
Laravel 9 Inertia Vue 3 Form Submit Example
Laravel 9 Inertia Vue 3 Search & Filter Example
Laravel 9 Install Inertia js Server-side rendering (SSR)
Laravel 9 with Inertia Vue 3 Implement Flash Message
Laravel Inertia Vue 3 Form Validation
Laravel 9 Inertia Vue 3 Pagination Example
How to Delete Record in Laravel 9 with Inertia Vue 3