Laravel 12 CRUD Application with Vue, InertiaJS & Tailwind CSS

Laravel 12 CRUD Application with Vue, InertiaJS & Tailwind CSS

In modern web development, combining Laravel, Vue.js, InertiaJS, and Tailwind CSS provides a seamless way to build fast, responsive, and dynamic single-page applications (SPAs). Laravel handles backend logic, Vue manages frontend reactivity, Inertia connects both sides smoothly, and Tailwind helps us style everything beautifully.

In this blog post, we’ll walk through building a full CRUD (Create, Read, Update, Delete) application in Laravel 12 with Vue 3, InertiaJS, and Tailwind CSS.


✨ What We’ll Build

A simple Posts Management System where users can:

  • View all posts
  • Add a new post
  • Edit a post
  • Delete a post

🔧 Tools & Versions

  • Laravel: 12.x
  • Vue.js: 3.x
  • Inertia.js: Latest
  • Tailwind CSS: 3.x
  • Vite: For asset bundling

🚀 Step 1: Install Laravel 12

Install a new Laravel project via Composer:

composer create-project laravel/laravel laravel-vue-crud
cd laravel-vue-crud

⚙️ Step 2: Install Inertia.js and Vue

Install Inertia Laravel adapter:

composer require inertiajs/inertia-laravel
php artisan inertia:middleware

Once the middleware has been published, append the HandleInertiaRequests middleware to the web middleware group in your application’s bootstrap/app.php file.

use App\Http\Middleware\HandleInertiaRequests;

->withMiddleware(function (Middleware $middleware) {
    $middleware->web(append: [
        HandleInertiaRequests::class,
    ]);
})

Root template

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0" />
    @vite('resources/js/app.js')
    @inertiaHead
  </head>
  <body>
    @inertia
  </body>
</html>

Install Vue and Inertia.js (client-side):

npm install @inertiajs/vue3

🎨 Step 3: Install and Configure Tailwind CSS

Install Tailwind CSS:

npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

Update tailwind.config.js:

content: [
  "./resources/**/*.blade.php",
  "./resources/**/*.js",
  "./resources/**/*.vue",
],

Add Tailwind directives in resources/css/app.css:

@tailwind base;
@tailwind components;
@tailwind utilities;

🧱 Step 4: Setup Vite + Vue + Inertia

Update resources/js/app.js:

import './bootstrap';

import { createApp, h } from 'vue'
import { createInertiaApp, Head, Link } from '@inertiajs/vue3'

createInertiaApp({
  title:(title) => ` Project Name ${title}`,
  resolve: name => {
    const pages = import.meta.glob('./Pages/**/*.vue', { eager: true })
    let page =  pages[`./Pages/${name}.vue`]
    page.default.layout = page.default.layout || Layout;
    return page;
  },
  setup({ el, App, props, plugin }) {
    createApp({ render: () => h(App, props) })
      .use(plugin)
      .component('Head',Head)
      .component('Link',Link)
      .mount(el)
  },progress: {
    color: 'red',
    includeCSS: true,
    showSpinner: false,
  },
  
})

Update vite.config.js:

import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': '/resources/js',
    },
  },
})

📁 Step 5: Create Post Model, Migration & Controller

Generate the model and migration:

php artisan make:model Post -m

Update the migration file:

Schema::create('posts', function (Blueprint $table) {
    $table->id();
    $table->string('title');
    $table->text('content');
    $table->timestamps();
});

Run migration:

php artisan migrate

Generate controller:

php artisan make:controller PostController --resource

🛠️ Step 6: Define Web Routes

In routes/web.php:

use App\Http\Controllers\PostController;

Route::get('/', fn () => redirect('/posts'));

Route::resource('posts', PostController::class);

📦 Step 7: Implement PostController with Inertia

Update PostController.php:

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::latest()->get(),
        ]);
    }

    public function create()
    {
        return Inertia::render('Posts/Create');
    }

    public function store(Request $request)
    {
        $request->validate([
            'title' => 'required',
            'content' => 'required',
        ]);

        Post::create($request->only('title', 'content'));

        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)
    {
        $request->validate([
            'title' => 'required',
            'content' => 'required',
        ]);

        $post->update($request->only('title', 'content'));

        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!');
    }
}

🧩 Step 8: Create Vue Pages

Create directory:

mkdir -p resources/js/Pages/Posts

📄 Index.vue

<template>
  <div class="p-6">
    <div class="flex justify-between items-center mb-4">
      <h1 class="text-2xl font-bold">All Posts</h1>
      <Link href="/posts/create" class="bg-blue-500 text-white px-4 py-2 rounded">Create</Link>
    </div>
    <div v-if="posts.length">
      <div v-for="post in posts" :key="post.id" class="border p-4 mb-2">
        <h2 class="font-bold text-lg">{{ post.title }}</h2>
        <p>{{ post.content }}</p>
        <div class="mt-2 space-x-2">
          <Link :href="`/posts/${post.id}/edit`" class="text-blue-500">Edit</Link>
          <button @click="deletePost(post.id)" class="text-red-500">Delete</button>
        </div>
      </div>
    </div>
    <div v-else>No posts found.</div>
  </div>
</template>

<script setup>
import { router } from '@inertiajs/inertia'
import { Link } from '@inertiajs/inertia-vue3'
defineProps(['posts'])

function deletePost(id) {
  if (confirm('Are you sure?')) {
    router.delete(`/posts/${id}`)
  }
}
</script>

📝 Create.vue and Edit.vue

Create a shared form component: PostForm.vue

<template>
  <form @submit.prevent="submit" class="space-y-4">
    <input v-model="form.title" type="text" placeholder="Title" class="w-full border p-2 rounded" />
    <textarea v-model="form.content" placeholder="Content" class="w-full border p-2 rounded"></textarea>
    <button type="submit" class="bg-green-500 text-white px-4 py-2 rounded">
      {{ isEdit ? 'Update' : 'Create' }} Post
    </button>
  </form>
</template>

<script setup>
import { useForm } from '@inertiajs/inertia-vue3'
import { computed } from 'vue'

const props = defineProps({ post: Object })
const isEdit = computed(() => !!props.post)

const form = useForm({
  title: props.post?.title || '',
  content: props.post?.content || '',
})

function submit() {
  if (isEdit.value) {
    form.put(`/posts/${props.post.id}`)
  } else {
    form.post('/posts')
  }
}
</script>

Use this in Create.vue:

<template>
  <div class="p-6">
    <h1 class="text-xl font-bold mb-4">Create Post</h1>
    <PostForm />
  </div>
</template>

<script setup>
import PostForm from './PostForm.vue'
</script>

And in Edit.vue:

<template>
  <div class="p-6">
    <h1 class="text-xl font-bold mb-4">Edit Post</h1>
    <PostForm :post="post" />
  </div>
</template>

<script setup>
import PostForm from './PostForm.vue'
defineProps({ post: Object })
</script>

🧠 Step 9: Handle Flash Messages

In app/Http/Middleware/HandleInertiaRequests.php, add shared flash:

'flash' => fn () => [
    'success' => session('success'),
],

Display flash in layout:

<template>
  <div>
    <div v-if="$page.props.flash.success" class="bg-green-100 text-green-800 p-3 rounded mb-4">
      {{ $page.props.flash.success }}
    </div>
    <slot />
  </div>
</template>

<script setup>
import { usePage } from '@inertiajs/inertia-vue3'
</script>

🧩 Final Steps

php artisan serve
npm run dev

✅ Conclusion

You’ve now built a fully functional CRUD application using Laravel 12, Vue 3, InertiaJS, and Tailwind CSS. This setup gives you the power of Laravel on the backend with the flexibility and responsiveness of Vue on the frontend — all without building a separate API.



Leave a Reply

Your email address will not be published. Required fields are marked *