In this tutorial, you will build a fully functional CRUD (Create, Read, Update, Delete) application in Laravel 12 using a resource controller, form request validation, and Blade templates.
composer create-project laravel/laravel laravel-crud
cd laravel-crud
Configure your database in the .env file:
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel_crud
DB_USERNAME=root
DB_PASSWORD=
php artisan make:model Post -mfc --resource
Open the generated migration and define the columns:
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->string('title');
$table->string('slug')->unique();
$table->text('body');
$table->boolean('published')->default(false);
$table->timestamps();
});
php artisan migrate
// app/Models/Post.php
class Post extends Model
{
protected $fillable = ['user_id', 'title', 'slug', 'body', 'published'];
protected function casts(): array
{
return ['published' => 'boolean'];
}
}
php artisan make:request StorePostRequest
php artisan make:request UpdatePostRequest
// StorePostRequest
public function rules(): array
{
return [
'title' => ['required', 'string', 'max:255', 'unique:posts,title'],
'body' => ['required', 'string'],
'published' => ['boolean'],
];
}
// UpdatePostRequest
public function rules(): array
{
return [
'title' => ['required', 'string', 'max:255', 'unique:posts,title,'.$this->post->id],
'body' => ['required', 'string'],
'published' => ['boolean'],
];
}
// app/Http/Controllers/PostController.php
use Illuminate\Support\Str;
class PostController extends Controller
{
public function index(): View
{
$posts = Post::latest()->paginate(10);
return view('posts.index', compact('posts'));
}
public function create(): View
{
return view('posts.create');
}
public function store(StorePostRequest $request): RedirectResponse
{
$data = $request->validated();
$data['user_id'] = auth()->id();
$data['slug'] = Str::slug($data['title']);
Post::create($data);
return redirect()->route('posts.index')->with('success', 'Post created!');
}
public function show(Post $post): View
{
return view('posts.show', compact('post'));
}
public function edit(Post $post): View
{
return view('posts.edit', compact('post'));
}
public function update(UpdatePostRequest $request, Post $post): RedirectResponse
{
$data = $request->validated();
$data['slug'] = Str::slug($data['title']);
$post->update($data);
return redirect()->route('posts.index')->with('success', 'Post updated!');
}
public function destroy(Post $post): RedirectResponse
{
$post->delete();
return redirect()->route('posts.index')->with('success', 'Post deleted!');
}
}
// routes/web.php
Route::middleware('auth')->resource('posts', PostController::class);
Create the directory resources/views/posts/ and add these files:
{{-- index.blade.php --}}
@extends('layouts.app')
@section('content')
<h1>Posts</h1>
<a href="{{ route('posts.create') }}">New Post</a>
@if (session('success')) <p>{{ session('success') }}</p> @endif
@foreach ($posts as $post)
<div>
<h2>{{ $post->title }}</h2>
<a href="{{ route('posts.edit', $post) }}">Edit</a>
<form method="POST" action="{{ route('posts.destroy', $post) }}">
@csrf @method('DELETE')
<button onclick="return confirm('Delete?')">Delete</button>
</form>
</div>
@endforeach
{{ $posts->links() }}
@endsection
{{-- create.blade.php --}}
@extends('layouts.app')
@section('content')
<h1>Create Post</h1>
<form method="POST" action="{{ route('posts.store') }}">
@csrf
<div>
<label>Title</label>
<input type="text" name="title" value="{{ old('title') }}">
@error('title') <p>{{ $message }}</p> @enderror
</div>
<div>
<label>Body</label>
<textarea name="body">{{ old('body') }}</textarea>
@error('body') <p>{{ $message }}</p> @enderror
</div>
<label>
<input type="checkbox" name="published" value="1" {{ old('published') ? 'checked' : '' }}> Publish
</label>
<button type="submit">Save</button>
</form>
@endsection
php artisan serve
Visit http://127.0.0.1:8000/posts to see your full CRUD application. You can create, read, update, and delete posts with validation and flash messages working out of the box.
All Comments