Laravel 12 CRUD Application with React, InertiaJS & Tailwind CSS

Author

Kritim Yantra

Feb 27, 2025

Laravel 12 CRUD Application with React, InertiaJS & Tailwind CSS
Welcome to this comprehensive guide on building a CRUD application for blog post management using the latest technologies: Laravel 12, React, InertiaJS, and Tailwind CSS. This step-by-step tutorial will cover setting up a new Laravel project with React and InertiaJS, configuring the database, defining models and controllers, building a responsive frontend with React and Tailwind CSS, and implementing full CRUD operations. Whether you're a beginner or an experienced developer, this guide will help you master these technologies through practical application. By the end, you'll have a fully functional blog post management system. Let's start building your own blog post manager!

1. Create a New Laravel Project

If you have the Laravel installer installed, create a new project by running:

laravel new laravelreactcrud

This command creates a fresh Laravel 12 application in a folder named laravelreactcrud.

2. Choose React as the Starter Kit

To leverage a modern frontend, we’ll choose react starter kit that provides React with InertiaJS.

3. Create the Model, Migration, and Controller

For our CRUD application, we will manage blog posts. Let’s create the Post model along with a migration and a resource controller.

Create the Post Model with a Migration:

php artisan make:model Post -m

This command creates a new Post model and a migration file for creating the posts table.

Create a Resource Controller for Posts:

php artisan make:controller PostController --resource

Next, open the generated migration file (located in database/migrations) and update it to include the necessary fields. For example:

// database/migrations/xxxx_xx_xx_create_posts_table.php

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

Run the migration to create the database table:

php artisan migrate

4. Build the Frontend with React and InertiaJS

Your React files for handling CRUD operations can be placed in your preferred directory (commonly under resources/js/Pages). Below are the key files and their code.

File: posts/index.tsx

// posts/index.tsx
import AppLayout from '@/layouts/app-layout';
import { type BreadcrumbItem } from '@/types';
import { Head, Link } from '@inertiajs/react';

const breadcrumbs: BreadcrumbItem[] = [
  {
    title: 'Posts',
    href: '/Posts',
  },
];

export default function Posts({ posts }) {
  return (
    <AppLayout breadcrumbs={breadcrumbs}>
      <Head title="Posts" />
      <div className="container mx-auto p-4">
        <div className="flex justify-between items-center mb-4">
          <h1 className="text-2xl font-bold">Blog Posts</h1>
          <button className="bg-gray-500 text-white px-4 py-1 rounded hover:bg-gray-600">
            <Link href="/posts/create">Create</Link>
          </button>
        </div>

        <div className="overflow-x-auto">
          <table className="min-w-full bg-white shadow rounded-lg">
            <thead>
              <tr className="bg-gray-200">
                <th className="py-2 px-4 text-left border-b">Name</th>
                <th className="py-2 px-4 text-left border-b">Content</th>
                <th className="py-2 px-4 text-left border-b">Actions</th>
              </tr>
            </thead>
            <tbody>
              {posts.map((post) => (
                <tr className="hover:bg-gray-50" key={post.id}>
                  <td className="py-2 px-4 border-b">{post.title}</td>
                  <td className="py-2 px-4 border-b">{post.content}</td>
                  <td className="py-2 px-4 border-b">
                    <button className="bg-green-500 text-white px-2 py-1 rounded hover:bg-green-600 mr-2">
                      <Link href={`/posts/${post.id}/edit`}>Edit</Link>
                    </button>
                    <Link
                      className="bg-red-500 text-white px-2 py-1 rounded hover:bg-red-600"
                      method="delete"
                      onClick={(e) => {
                        if (!confirm('Are you sure?')) {
                          e.preventDefault();
                        }
                      }}
                      href={route('posts.destroy', post.id)}
                    >
                      Delete
                    </Link>
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </div>
    </AppLayout>
  );
}





File: posts/create.tsx

// posts/create.tsx
import AppLayout from '@/layouts/app-layout';
import { type BreadcrumbItem } from '@/types';
import { Head, useForm, Link } from '@inertiajs/react';

const breadcrumbs: BreadcrumbItem[] = [
  {
    title: 'Posts',
    href: '/Posts',
  },
];

export default function Posts() {
  const { data, setData, errors, post, reset, processing } = useForm({
    title: '',
    content: '',
  });

  function submit(e: React.FormEvent) {
    e.preventDefault();
    post(route('posts.store'), {
      preserveScroll: true,
      onSuccess: () => reset(),
    });
  }

  return (
    <AppLayout breadcrumbs={breadcrumbs}>
      <Head title="Posts" />
      <div className="container mx-auto p-4">
        <div className="flex justify-between items-center mb-4">
          <h1 className="text-2xl font-bold">Blog Posts</h1>
        </div>

        <div className="bg-white shadow rounded-lg p-4 mb-6">
          <h2 className="text-xl font-bold mb-4">Add New Post</h2>
          <form onSubmit={submit}>
            <div className="mb-4">
              <label htmlFor="name" className="block text-gray-700 font-medium mb-1">
                Title
              </label>
              <input
                type="text"
                id="name"
                name="title"
                value={data.title}
                onChange={(e) => setData('title', e.currentTarget.value)}
                placeholder="Post Name"
                className="w-full border border-gray-300 rounded px-3 py-2 focus:outline-none focus:ring focus:ring-blue-200"
              />
            </div>
            <div className="mb-4">
              <label htmlFor="content" className="block text-gray-700 font-medium mb-1">
                Content
              </label>
              <textarea
                id="content"
                name="content"
                value={data.content}
                onChange={(e) => setData('content', e.currentTarget.value)}
                placeholder="Post Content"
                rows={4}
                className="w-full border border-gray-300 rounded px-3 py-2 focus:outline-none focus:ring focus:ring-blue-200"
              ></textarea>
            </div>
            <button
              type="submit"
              disabled={processing}
              className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
            >
              {processing ? 'Saving...' : 'Save'}
            </button>
          </form>
        </div>
      </div>
    </AppLayout>
  );
}

File: posts/edit.tsx

// posts/edit.tsx
import AppLayout from '@/layouts/app-layout';
import { type BreadcrumbItem } from '@/types';
import { Head, useForm, Link } from '@inertiajs/react';

const breadcrumbs: BreadcrumbItem[] = [
  {
    title: 'Posts',
    href: '/Posts',
  },
];

export default function Posts({ post }) {
  const { data, setData, errors, put, reset, processing } = useForm({
    title: post.title,
    content: post.content,
  });

  function submit(e: React.FormEvent) {
    e.preventDefault();
    put(route('posts.update', post.id), {
      preserveScroll: true,
      onSuccess: () => reset(),
    });
  }

  return (
    <AppLayout breadcrumbs={breadcrumbs}>
      <Head title="Posts" />
      <div className="container mx-auto p-4">
        <div className="flex justify-between items-center mb-4">
          <h1 className="text-2xl font-bold">Blog Posts</h1>
        </div>

        <div className="bg-white shadow rounded-lg p-4 mb-6">
          <h2 className="text-xl font-bold mb-4">Edit Post</h2>
          <form onSubmit={submit}>
            <div className="mb-4">
              <label htmlFor="name" className="block text-gray-700 font-medium mb-1">
                Title
              </label>
              <input
                type="text"
                id="name"
                name="title"
                value={data.title}
                onChange={(e) => setData('title', e.currentTarget.value)}
                placeholder="Post Name"
                className="w-full border border-gray-300 rounded px-3 py-2 focus:outline-none focus:ring focus:ring-blue-200"
              />
            </div>
            <div className="mb-4">
              <label htmlFor="content" className="block text-gray-700 font-medium mb-1">
                Content
              </label>
              <textarea
                id="content"
                name="content"
                value={data.content}
                onChange={(e) => setData('content', e.currentTarget.value)}
                placeholder="Post Content"
                rows={4}
                className="w-full border border-gray-300 rounded px-3 py-2 focus:outline-none focus:ring focus:ring-blue-200"
              ></textarea>
            </div>
            <button
              type="submit"
              disabled={processing}
              className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-600"
            >
              {processing ? 'Updating...' : 'Update'}
            </button>
          </form>
        </div>
      </div>
    </AppLayout>
  );
}

File: Dashboard.tsx

// Dashboard.tsx
import { Link } from '@inertiajs/react';
import { Button } from '@/components/ui/button';

export default function Dashboard() {
  return (
    <div className="p-4">
      <Button key="posts">
        <Link href="/posts">Posts</Link>
      </Button>
    </div>
  );
}

5. Build the Backend Controller

Create a controller to handle all CRUD operations. The PostController.php file should look like this:

// PostController.php
<?php

namespace App\Http\Controllers;

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

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

    public function store(Request $request)
    {
        Post::create([
            'title'   => $request->title,
            'content' => $request->content,
        ]);

        return redirect()->route('posts.index');
    }

    public function edit(Post $post)
    {
        return Inertia::render('posts/edit', [
            'post' => $post,
        ]);
    }

    public function update(Request $request, Post $post)
    {
        $post->update([
            'title'   => $request->title,
            'content' => $request->content,
        ]);

        return redirect()->route('posts.index');
    }

    public function destroy(Post $post)
    {
        $post->delete();

        return redirect()->route('posts.index');
    }
}

6. Final Steps

Routes: Define your resource routes in your routes/web.php file. For example:

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

Serve Your Application: Start your local development server:

php artisan serve

Now, navigate to your application in your browser and you should be able to create, read, update, and delete posts using a smooth React interface powered by InertiaJS and styled with Tailwind CSS.

Conclusion

In this guide, we walked through every step—from creating your Laravel project using the Laravel installer to setting up a React-based CRUD application with InertiaJS. By leveraging Laravel 12’s starter kits and combining them with React, InertiaJS, and Tailwind CSS, you can build a modern, responsive, and user-friendly web application in no time.

Happy coding, and enjoy building your Laravel React CRUD application!

Tags

Laravel React Php

Comments

No comments yet. Be the first to comment!

Please log in to post a comment:

Continue with Google