Laravel 12 CRUD Application with React, InertiaJS & Tailwind CSS
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!
Projects to explore next
Source-code projects from the KritiMyantra catalogue. Check each project's details before buying.
Flask Authentication System (Free Version) - Kritim Yantra
🛠 Features✔ Google & GitHub Authentication✔ User Registration & Login✔ Profile/Dashboard Access✔ Secure & Mi...
Flask Blog CMS with Admin Dashboard — Python Source Code
A Flask-based blogging website with a built-in admin dashboard. Manage articles, images, categories, tags and featured p...
Laravel 12 + ReactJS Starter Kit: Add Multi-Language Support (Arabic, English, Spanish) with Blog CRUD
🚀 Installation & Setup Download & Extract Get the ZIP from kritimyantra.com and extract it. Includes preconfigu...
Comments
No comments yet. Be the first to comment.
Sign in to join the discussion.