Laravel - Vue - Basic CRUD

Category : Laravel - Vue (Explore) Publish At : 23 Apr, 2025

Share On :

Make Category Model & Migration

<?php


Schema::create('categories', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->text('description')->nullable();
$table->timestamps();
});

Make controller CategoryController

<?php

class CategoryController extends Controller
{
public function index()
{
return Inertia::render('Categories/Index', [
'categories' => Category::all(),
]);
}
public function create()
{
return Inertia::render('Categories/Create');
}
public function store(Request $request)
{
$request->validate([
'name' => 'required|string|max:255',
'description' => 'nullable|string',
]);
Category::create($request->all());
return redirect()->route('categories.index')->with('success', 'Category created successfully.');
}
public function edit(Category $category)
{
return Inertia::render('Categories/Edit', [
'category' => $category,
]);
}
public function update(Request $request, Category $category)
{
$request->validate([
'name' => 'required|string|max:255',
'description' => 'nullable|string',
]);
$category->update($request->all());
return redirect()->route('categories.index')->with('success', 'Category updated successfully.');
}
public function destroy(Category $category)
{
$category->delete();
return redirect()->route('categories.index')->with('success', 'Category deleted successfully.');
}
}

Create Three Files, resources/js/Pages/Category/Index.vue, resources/js/Pages/Category/Create.vue & resources/js/Pages/Category/Edit.vue

<!-- Index.vue -->
<template>


<Head title="Categories" /><AuthenticatedLayout><template #header><div class="flex items-center justify-between"><h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
Categories
</h2><PrimaryButton><Link href="/categories/create" class="btn btn-primary">+ Create Category</Link></PrimaryButton></div></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 dark:bg-gray-800"><div class="p-6 text-gray-900 dark:text-gray-100"><div><!-- <div v-if="$page.props.flash.success" class="alert alert-success">
{{ $page.props.flash.success }}
</div> -->
<Alert v-if="$page.props.flash.success" type="success" :message="$page.props.flash.success" />


<table class="table-auto w-full border border-gray-200 dark:border-gray-700"><thead class="bg-gray-100 dark:bg-gray-700"><tr><th class="px-4 py-2 text-left text-gray-600 dark:text-gray-300">ID</th><th class="px-4 py-2 text-left text-gray-600 dark:text-gray-300">Name</th><th class="px-4 py-2 text-left text-gray-600 dark:text-gray-300">Description</th><th class="px-4 py-2 text-left text-gray-600 dark:text-gray-300">Actions</th></tr></thead><tbody><tr v-for="category in categories" :key="category.id" class="border-t border-gray-200 dark:border-gray-700"><td class="px-4 py-2">{{ category.id }}</td><td class="px-4 py-2">{{ category.name }}</td><td class="px-4 py-2">{{ category.description }}</td><td><button class="text-indigo-600 hover:underline"><Link :href="`/categories/${category.id}/edit`" class="btn btn-sm btn-warning me-2">Edit</Link></button>

<button @click="deleteCategory(category.id)" class="btn btn-sm btn-danger">Delete</button></td></tr></tbody></table></div></div></div></div></div>


</AuthenticatedLayout>
</template>


<script setup>
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout.vue';
import { Link, router } from '@inertiajs/vue3';
import { Head } from '@inertiajs/vue3';
import PrimaryButton from '@/Components/PrimaryButton.vue';
import Alert from '@/Components/Alert.vue'




defineProps({
categories: Array,
});


const deleteCategory = (id) => {
if (confirm('Are you sure you want to delete this category?')) {
router.delete(`/categories/${id}`);
}
};
</script>



<!-- Create.vue -->
<template>


<Head title="Create Category" /><AuthenticatedLayout><template #header><h2class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200"
>
Create Category
</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 dark:bg-gray-800"><div class="p-6 text-gray-900 dark:text-gray-100"><div><form @submit.prevent="submit"><div class="mb-3"><InputLabel value="Name" /><TextInput v-model="form.name" id="name" class="mt-1 block w-full" /><InputError class="mt-2" :message="form.errors.name" /><!-- <div v-if="form.errors.name" class="text-danger">{{ form.errors.name }}</div> --></div><div class="mb-3"><label for="description" class="form-label">Description</label><!-- <textarea v-model="form.description" class="form-control" id="description"></textarea> --><TextInput v-model="form.description" id="description" class="mt-1 block w-full" /><!-- <TextInput v-model="form.description" descriptionid="name" /> --></div><SecondaryButton type="submit" value="Save":disabled="form.processing" >
Save
</SecondaryButton>

<Link href="/categories" class="btn btn-secondary ms-2">Cancel</Link></form></div></div></div></div></div>


</AuthenticatedLayout>
</template>


<script setup>
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout.vue';
import TextInput from '@/Components/TextInput.vue';
import InputLabel from '@/Components/InputLabel.vue';
import InputError from '@/Components/InputError.vue';
import SecondaryButton from '@/Components/SecondaryButton.vue';
import { Link, router } from '@inertiajs/vue3';
import { Head } from '@inertiajs/vue3';
import { useForm } from '@inertiajs/vue3';


const form = useForm({
name: '',
description: '',
});


const submit = () => {
form.post('/categories');
};
</script>



<!-- Edit.vue -->
<template>


<Head title="Update Category" /><AuthenticatedLayout><template #header><h2 class="text-xl font-semibold leading-tight text-gray-800 dark:text-gray-200">
Update Category
</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 dark:bg-gray-800"><div class="p-6 text-gray-900 dark:text-gray-100"><form @submit.prevent="submit"><div class="mb-3"><InputLabel value="Name" /><TextInput v-model="form.name" id="name" class="mt-1 block w-full" /><InputError class="mt-2" :message="form.errors.name" /><!-- <label for="name" class="form-label">Name</label>
<input type="text" class="form-control" id="name" required>
<div v-if="form.errors.name" class="text-danger">{{ form.errors.name }}</div> -->
</div><!-- <div class="mb-3">
<label for="description" class="form-label">Description</label>
<textarea v-model="form.description" class="form-control" id="description"></textarea>
</div> -->
<div class="mb-3"><label for="description" class="form-label">Description</label><!-- <textarea v-model="form.description" class="form-control" id="description"></textarea> --><TextInput v-model="form.description" id="description" class="mt-1 block w-full" /><!-- <TextInput v-model="form.description" descriptionid="name" /> --></div><SecondaryButton type="submit" value="Save" :disabled="form.processing">
Save
</SecondaryButton>


<Link href="/categories" class="btn btn-secondary ms-2">Cancel</Link></form></div></div></div></div>


</AuthenticatedLayout>
</template>


<script setup>
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout.vue';
import TextInput from '@/Components/TextInput.vue';
import InputLabel from '@/Components/InputLabel.vue';
import InputError from '@/Components/InputError.vue';
import SecondaryButton from '@/Components/SecondaryButton.vue';
import { Link, router } from '@inertiajs/vue3';
import { Head } from '@inertiajs/vue3';
import { useForm } from '@inertiajs/vue3';


const props = defineProps({
category: Object,
});


const form = useForm({
name: props.category.name,
description: props.category.description,
});


const submit = () => {
form.put(`/categories/${props.category.id}`);
};
</script>

added following lines in web.php

Route::resource('categories', CategoryController::class);


To Get Demo Visit https://github.com/vivek-mistry/ExploreLaravelVue.git