In this tutorial, we will create simple CRUD using Laravel Inertia Js with vue 3. Inertia JS is a new approach to building SPA for web apps. For this section we will using laravel 8 with laravel breeze , it come with useful feature like tailwind css, alpine js, blade components and we can also install inertia with vue ,react,Svelte. For CRUD we will use inertia vue 3.
Step 1: Install Laravel & Connect Database
Step 2: Install Breeze & Setup Inertia Js Vue 3
Step 3: Create Model Factory and Resource Controller & route
Step 4: Create a view file for CRUD
Step 1: Install Laravel & Connect Database
Installing a fresh new laravel application, so head over to the terminal, type the command, and create a new laravel app.
composer create-project --prefer-dist laravel/laravel inertia-vue-3
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 Model Factory and Resource Controller & route
First we need to create Post model migration table and factory for fake data & resource post controller, So we will create single line command .
php artisan make:model Post -mfrc
Model created successfully.
Factory created successfully.
Created Migration: 2021_09_14_112455_create_posts_table
Controller created successfully.
app/database/migrations/posts_table.php
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreatePostsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('description');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('posts');
}
}
app/Models/Post.php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
protected $fillable = [
'title',
'description'
];
use HasFactory;
}
app/database/factories/PostFactory.php
<?php
namespace Database\Factories;
use App\Models\Post;
use Illuminate\Database\Eloquent\Factories\Factory;
class PostFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* @var string
*/
protected $model = Post::class;
/**
* Define the model's default state.
*
* @return array
*/
public function definition()
{
return [
'title' => $this->faker->title(),
'description' => $this->faker->text(),
];
}
}
After set up factory we need to create fake data, So we will use laravel tinker
Create fake data using tinker
php artisan ti
Psy Shell v0.10.8 (PHP 7.4.23 — cli) by Justin Hileman >>> App\Models\Post::factory(30)->create();
app/Http/Controllers/PostController.php
<?php
namespace App\Http\Controllers;
use App\Models\Post;
use Illuminate\Support\Facades\Redirect;
use Illuminate\Support\Facades\Request;
use Inertia\Inertia;
class PostController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$posts = Post::latest()->paginate(10);
return Inertia::render('Post/Index', ['posts' => $posts]);
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
return Inertia::render('Post/Create');
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
Post::create(
Request::validate([
'title' => ['required', 'max:90'],
'description' => ['required'],
])
);
return Redirect::route('posts.index');
}
/**
* Display the specified resource.
*
* @param \App\Models\Post $post
* @return \Illuminate\Http\Response
*/
public function show(Post $post)
{
//
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Post $post
* @return \Illuminate\Http\Response
*/
public function edit(Post $post)
{
return Inertia::render('Post/Edit', [
'post' => [
'id' => $post->id,
'title' => $post->title,
'description' => $post->description
]
]);
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param \App\Models\Post $post
* @return \Illuminate\Http\Response
*/
public function update(Request $request, Post $post)
{
$data = Request::validate([
'title' => ['required', 'max:90'],
'description' => ['required'],
]);
$post->update($data);
return Redirect::route('posts.index');
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Post $post
* @return \Illuminate\Http\Response
*/
public function destroy(Post $post)
{
$post->delete();
return Redirect::route('posts.index');
}
}
app/routes/web.php
<?php
use App\Http\Controllers\PostController;
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('posts', PostController::class);
require __DIR__.'/auth.php';
Step 4: Create a view file for CRUD
app/resources/js/Pages/Post/Index.vue
<template>
<Head title="Dashboard" />
<BreezeAuthenticatedLayout>
<template #header>
<h2 class="text-xl font-semibold leading-tight text-gray-800">
Post
</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">
<div class="mb-4">
<Link
class="
px-6
py-2
mb-2
text-green-100
bg-green-500
rounded
"
:href="route('posts.create')"
>
Posts Create
</Link>
</div>
<table>
<thead class="font-bold bg-gray-300 border-b-2">
<td class="px-4 py-2">ID</td>
<td class="px-4 py-2">Title</td>
<td class="px-4 py-2">Description</td>
<td class="px-4 py-2">Action</td>
</thead>
<tbody>
<tr v-for="post in posts.data" :key="post.id">
<td class="px-4 py-2">{{ post.id }}</td>
<td class="px-4 py-2">{{ post.title }}</td>
<td class="px-4 py-2">
{{ post.description }}
</td>
<td class="px-4 py-2 font-extrabold">
<Link
class="text-green-700"
:href="route('posts.edit', post.id)"
>
Edit
</Link>
<Link
@click="destroy(post.id)"
class="text-red-700"
>Delete</Link
>
</td>
</tr>
</tbody>
</table>
<pagination :links="posts.links" />
</div>
</div>
</div>
</div>
</BreezeAuthenticatedLayout>
</template>
<script>
import BreezeAuthenticatedLayout from "@/Layouts/Authenticated.vue";
import BreezeNavLink from "@/Components/NavLink.vue";
import { Head } from "@inertiajs/inertia-vue3";
import { Link } from "@inertiajs/inertia-vue3";
export default {
components: {
BreezeAuthenticatedLayout,
Head,
BreezeNavLink,
Link,
},
props: {
posts: Object,
},
methods: {
destroy(id) {
this.$inertia.delete(route("posts.destroy", id));
},
},
};
</script>
app/resources/js/Pages/Post/Create.vue
<template>
<Head title="Dashboard" />
<BreezeAuthenticatedLayout>
<template #header>
<h2 class="text-xl font-semibold leading-tight text-gray-800">
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>
<label for="title">Title</label>
<input
type="text"
v-model="form.title"
class="
w-full
px-4
py-2
mt-2
border
rounded-md
focus:outline-none
focus:ring-1
focus:ring-blue-600
"
/>
</div>
<div class="mt-4">
<label for="title">Description</label>
<textarea
name="description"
type="text"
v-model="form.description"
class="
w-full
px-4
py-2
mt-2
border
rounded-md
focus:outline-none
focus:ring-1
focus:ring-blue-600
"
>
</textarea>
</div>
<!-- submit -->
<div class="flex items-center mt-4">
<button
class="
px-6
py-2
text-white
bg-gray-900
rounded
"
>
Save
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</BreezeAuthenticatedLayout>
</template>
<script>
import BreezeAuthenticatedLayout from "@/Layouts/Authenticated.vue";
import BreezeLabel from "@/Components/Label";
import { Head } from "@inertiajs/inertia-vue3";
import { useForm } from "@inertiajs/inertia-vue3";
export default {
components: {
BreezeAuthenticatedLayout,
Head,
},
setup() {
const form = useForm({
title: null,
description: null,
});
return { form };
},
methods: {
submit() {
this.form.post(route("posts.store"));
},
},
};
</script>
app/resources/js/Pages/Post/Edit.vue
<template>
<Head title="Dashboard" />
<BreezeAuthenticatedLayout>
<template #header>
<h2 class="text-xl font-semibold leading-tight text-gray-800">
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>
<label for="title">Title</label>
<input
type="text"
v-model="form.title"
class="
w-full
px-4
py-2
mt-2
border
rounded-md
focus:outline-none
focus:ring-1
focus:ring-blue-600
"
/>
</div>
<div>
<label for="title">Description</label>
<textarea
name="description"
type="text"
v-model="form.description"
class="
w-full
px-4
py-2
mt-2
border
rounded-md
focus:outline-none
focus:ring-1
focus:ring-blue-600
"
>
</textarea>
</div>
<!-- submit -->
<div class="flex items-center mt-4">
<button
class="
px-6
py-2
text-white
bg-gray-900
rounded
"
>
Save
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</BreezeAuthenticatedLayout>
</template>
<script>
import BreezeAuthenticatedLayout from "@/Layouts/Authenticated.vue";
import { Head } from "@inertiajs/inertia-vue3";
import { useForm } from "@inertiajs/inertia-vue3";
export default {
components: {
BreezeAuthenticatedLayout,
Head,
},
setup(props) {
const form = useForm({
title: props.post.title,
description: props.post.description,
});
return { form };
},
props: {
post: Object,
},
methods: {
submit() {
this.form.put(route("posts.update", this.post.id));
},
},
};
</script>
Note: Always try to open to terminal window one use for server and second for below command.
npm run dev
Read also
Laravel 9 Inertia Vue 3 DataTables Example
How to Install Tailwind CSS in Vue 3
Tailwind CSS Vue 3 Modal Examples
Build A Simple Tabs with Tailwind CSS & Vue 3
Vue 3 Responsive Navbar Menu With Tailwind CSS Example
Building Reusable Vue 3 Button Component Using Tailwind CSS