An updated Laravel CRUD Generator version of ibex/crud-generator now generates CRUD in Tailwind CSS in the blade with the blank Laravel installation.
1- Install Laravel
composer create-project laravel/laravel laravel-11-crud
2- Create Migration
php artisan make:model Posts –migration
For example posts table has the following columns in migration below.
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->string('description');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('posts');
}
};
3- Install ibex/crud-generator
php artisan make:crud posts tailwind
A single command will generate all CRUD operations in a second. This will include a resource PostController, Post (Model), PostRequest, and all Blade views in Tailwind CSS.
4- Add route in web.php
Route::resource(‘posts’, PostController::class);
Below is the code generated for PostController
<?php
namespace App\Http\Controllers;
use App\Models\Post;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use App\Http\Requests\PostRequest;
use Illuminate\Support\Facades\Redirect;
use Illuminate\View\View;
class PostController extends Controller
{
public function index(Request $request): View
{
$posts = Post::paginate();
return view('post.index', compact('posts'))
->with('i', ($request->input('page', 1) - 1) * $posts->perPage());
}
public function create(): View
{
$post = new Post();
return view('post.create', compact('post'));
}
public function store(PostRequest $request): RedirectResponse
{
Post::create($request->validated());
return Redirect::route('posts.index')
->with('success', 'Post created successfully.');
}
public function show($id): View
{
$post = Post::find($id);
return view('post.show', compact('post'));
}
public function edit($id): View
{
$post = Post::find($id);
return view('post.edit', compact('post'));
}
public function update(PostRequest $request, Post $post): RedirectResponse
{
$post->update($request->validated());
return Redirect::route('posts.index')
->with('success', 'Post updated successfully');
}
public function destroy($id): RedirectResponse
{
Post::find($id)->delete();
return Redirect::route('posts.index')
->with('success', 'Post deleted successfully');
}
}
5- Create view files.
6-Create view file- create.blade.php
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ __('Create') }} {{modelTitle}}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-full mx-auto sm:px-6 lg:px-8 space-y-6">
<div class="p-4 sm:p-8 bg-white shadow sm:rounded-lg">
<div class="w-full">
<div class="sm:flex sm:items-center">
<div class="sm:flex-auto">
<h1 class="text-base font-semibold leading-6 text-gray-900">{{ __('Create') }} {{modelTitle}}</h1>
<p class="mt-2 text-sm text-gray-700">Add a new {{ __('{{modelTitle}}') }}.</p>
</div>
<div class="mt-4 sm:ml-16 sm:mt-0 sm:flex-none">
<a type="button" href="{{ route('{{modelRoute}}.index') }}" class="block rounded-md bg-indigo-600 px-3 py-2 text-center text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">Back</a>
</div>
</div>
<div class="flow-root">
<div class="mt-8 overflow-x-auto">
<div class="max-w-xl py-2 align-middle">
<form method="POST" action="{{ route('{{modelRoute}}.store') }}" role="form" enctype="multipart/form-data">
@csrf
@include('{{modelView}}.form')
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</x-app-layout>
7- Create view file- edit.blade.php
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ __('Update') }} {{modelTitle}}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-full mx-auto sm:px-6 lg:px-8 space-y-6">
<div class="p-4 sm:p-8 bg-white shadow sm:rounded-lg">
<div class="w-full">
<div class="sm:flex sm:items-center">
<div class="sm:flex-auto">
<h1 class="text-base font-semibold leading-6 text-gray-900">{{ __('Update') }} {{modelTitle}}</h1>
<p class="mt-2 text-sm text-gray-700">Update existing {{ __('{{modelTitle}}') }}.</p>
</div>
<div class="mt-4 sm:ml-16 sm:mt-0 sm:flex-none">
<a type="button" href="{{ route('{{modelRoute}}.index') }}" class="block rounded-md bg-indigo-600 px-3 py-2 text-center text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">Back</a>
</div>
</div>
<div class="flow-root">
<div class="mt-8 overflow-x-auto">
<div class="max-w-xl py-2 align-middle">
<form method="POST" action="{{ route('{{modelRoute}}.update', ${{modelNameLowerCase}}->id) }}" role="form" enctype="multipart/form-data">
{{ method_field('PATCH') }}
@csrf
@include('{{modelView}}.form')
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</x-app-layout>
8-Create view file- form-field.blade.php
<div>
<x-input-label for="{{column_snake}}" :value="__('{{title}}')"/>
<x-text-input id="{{column_snake}}" name="{{column}}" type="text" class="mt-1 block w-full" :value="old('{{column}}', ${{modelNameLowerCase}}?->{{column}})" autocomplete="{{column}}" placeholder="{{title}}"/>
<x-input-error class="mt-2" :messages="$errors->get('{{column}}')"/>
</div>
9-Create view file- form.blade.php
<div class="space-y-6">
{{form}}
<div class="flex items-center gap-4">
<x-primary-button>Submit</x-primary-button>
</div>
</div>
10-Create view file- index.blade.php
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ __('{{modelTitlePlural}}') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-full mx-auto sm:px-6 lg:px-8 space-y-6">
<div class="p-4 sm:p-8 bg-white shadow sm:rounded-lg">
<div class="w-full">
<div class="sm:flex sm:items-center">
<div class="sm:flex-auto">
<h1 class="text-base font-semibold leading-6 text-gray-900">{{ __('{{modelTitlePlural}}') }}</h1>
<p class="mt-2 text-sm text-gray-700">A list of all the {{ __('{{modelTitlePlural}}') }}.</p>
</div>
<div class="mt-4 sm:ml-16 sm:mt-0 sm:flex-none">
<a type="button" href="{{ route('{{modelRoute}}.create') }}" class="block rounded-md bg-indigo-600 px-3 py-2 text-center text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">Add new</a>
</div>
</div>
<div class="flow-root">
<div class="mt-8 overflow-x-auto">
<div class="inline-block min-w-full py-2 align-middle">
<table class="w-full divide-y divide-gray-300">
<thead>
<tr>
<th scope="col" class="py-3 pl-4 pr-3 text-left text-xs font-semibold uppercase tracking-wide text-gray-500">No</th>
{{tableHeader}}
<th scope="col" class="px-3 py-3 text-left text-xs font-semibold uppercase tracking-wide text-gray-500"></th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white">
@foreach (${{modelNamePluralLowerCase}} as ${{modelNameLowerCase}})
<tr class="even:bg-gray-50">
<td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-semibold text-gray-900">{{ ++$i }}</td>
{{tableBody}}
<td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900">
<form action="{{ route('{{modelRoute}}.destroy', ${{modelNameLowerCase}}->id) }}" method="POST">
<a href="{{ route('{{modelRoute}}.show', ${{modelNameLowerCase}}->id) }}" class="text-gray-600 font-bold hover:text-gray-900 mr-2">{{ __('Show') }}</a>
<a href="{{ route('{{modelRoute}}.edit', ${{modelNameLowerCase}}->id) }}" class="text-indigo-600 font-bold hover:text-indigo-900 mr-2">{{ __('Edit') }}</a>
@csrf
@method('DELETE')
<a href="{{ route('{{modelRoute}}.destroy', ${{modelNameLowerCase}}->id) }}" class="text-red-600 font-bold hover:text-red-900" onclick="event.preventDefault(); confirm('Are you sure to delete?') ? this.closest('form').submit() : false;">{{ __('Delete') }}</a>
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
<div class="mt-4 px-4">
{!! ${{modelNamePluralLowerCase}}->withQueryString()->links() !!}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</x-app-layout>
11- Create view file- show.blade.php
<x-app-layout>
<x-slot name="header">
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
{{ ${{modelNameLowerCase}}->name ?? __('Show') . " " . __('{{modelTitle}}') }}
</h2>
</x-slot>
<div class="py-12">
<div class="max-w-full mx-auto sm:px-6 lg:px-8 space-y-6">
<div class="p-4 sm:p-8 bg-white shadow sm:rounded-lg">
<div class="w-full">
<div class="sm:flex sm:items-center">
<div class="sm:flex-auto">
<h1 class="text-base font-semibold leading-6 text-gray-900">{{ __('Show') }} {{modelTitle}}</h1>
<p class="mt-2 text-sm text-gray-700">Details of {{ __('{{modelTitle}}') }}.</p>
</div>
<div class="mt-4 sm:ml-16 sm:mt-0 sm:flex-none"> <a type="button" href="{{ route('{{modelRoute}}.index') }}" class="block rounded-md bg-indigo-600 px-3 py-2 text-center text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">Back</a>
</div>
</div>
<div class="flow-root">
<div class="mt-8 overflow-x-auto">
<div class="inline-block min-w-full py-2 align-middle">
<div class="mt-6 border-t border-gray-100">
<dl class="divide-y divide-gray-100">
{{viewRows}}
</dl>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</x-app-layout>
12-Create view file- view-field.blade.php
<div class="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
<dt class="text-sm font-medium leading-6 text-gray-900">{{title}}</dt>
<dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">{{ ${{modelNameLowerCase}}->{{column}} }}</dd>
</div>
End to End Technology Solutions