Laravel

CRUD & Validation

Build full Create, Read, Update, Delete functionality using Resource Controllers, Form Requests, and Blade forms with proper validation.

Generate Resource Controller: Use Artisan to scaffold a resource controller with all CRUD methods and optionally bind it to a model via --model.
📄terminal
BASH
# Generate resource controller with model binding
php artisan make:controller PostController --resource --model=Post

# Generate Form Request classes for store and update
php artisan make:request StorePostRequest
php artisan make:request UpdatePostRequest

# Generate model with migration, factory, and seeder
php artisan make:model Post -mfs
Route Resource Declaration: Register all 7 RESTful routes (index, create, store, show, edit, update, destroy) with a single Route::resource() call. Use only or except to limit routes.
📄routes/web.php
PHP
<?php

use App\Http\Controllers\PostController;
use Illuminate\Support\Facades\Route;

// Full resource – generates index, create, store, show, edit, update, destroy
Route::resource('posts', PostController::class);

// Limit to specific actions
Route::resource('posts', PostController::class)->only(['index', 'show']);
Route::resource('posts', PostController::class)->except(['destroy']);

// Nested resource (e.g. post comments)
Route::resource('posts.comments', CommentController::class)->shallow();

// Named middleware group
Route::middleware(['auth'])->group(function () {
    Route::resource('posts', PostController::class)->except(['index', 'show']);
});
Full Resource Controller: Complete implementation of all CRUD methods. Uses Form Request classes for validation, redirects with flash messages, and model route binding.
📄app/Http/Controllers/PostController.php
PHP
<?php

namespace App\Http\Controllers;

use App\Http\Requests\StorePostRequest;
use App\Http\Requests\UpdatePostRequest;
use App\Models\Post;
use Illuminate\Http\RedirectResponse;
use Illuminate\View\View;

class PostController extends Controller
{
    public function index(): View
    {
         = Post::latest()->paginate(10);
        return view('posts.index', compact('posts'));
    }

    public function create(): View
    {
        return view('posts.create');
    }

    public function store(StorePostRequest $request): RedirectResponse
    {
        Post::create($request->validated());

        return redirect()->route('posts.index')
            ->with('success', 'Post created successfully.');
    }

    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
    {
        $post->update($request->validated());

        return redirect()->route('posts.show', $post)
            ->with('success', 'Post updated successfully.');
    }

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

        return redirect()->route('posts.index')
            ->with('success', 'Post deleted successfully.');
    }
}
Form Request Validation: Encapsulate authorization and validation logic in dedicated FormRequest classes. The authorize() method controls access; rules() defines validation constraints; messages() customizes error text.
📄app/Http/Requests/StorePostRequest.php
PHP
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class StorePostRequest extends FormRequest
{
    public function authorize(): bool
    {
        // Allow all authenticated users
        return true;
        // Or check policy:  return $this->user()->can('create', Post::class);
    }

    public function rules(): array
    {
        return [
            'title'      => ['required', 'string', 'min:5', 'max:255'],
            'slug'       => ['required', 'string', 'unique:posts,slug'],
            'body'       => ['required', 'string', 'min:20'],
            'status'     => ['required', 'in:draft,published'],
            'image'      => ['nullable', 'image', 'mimes:jpg,jpeg,png,webp', 'max:2048'],
            'tags'       => ['nullable', 'array'],
            'tags.*'     => ['exists:tags,id'],
            'published_at' => ['nullable', 'date', 'after_or_equal:today'],
        ];
    }

    public function messages(): array
    {
        return [
            'title.required' => 'A post title is required.',
            'title.min'      => 'The title must be at least 5 characters.',
            'slug.unique'    => 'This slug is already taken.',
            'body.min'       => 'Post content must be at least 20 characters.',
            'image.max'      => 'Image size must not exceed 2 MB.',
        ];
    }

    // Optionally transform input before validation
    protected function prepareForValidation(): void
    {
        $this->merge([
            'slug' => \Illuminate\Support\Str::slug($this->slug ?? $this->title),
        ]);
    }
}
Common Validation Rules Reference: A quick-reference list of frequently used Laravel validation rules for strings, numbers, dates, files, and database checks.
📄validation-rules-reference.php
PHP
// String rules
'name'     => ['required', 'string', 'max:100', 'alpha_dash'],
'email'    => ['required', 'email:rfc,dns', 'unique:users,email'],
'password' => ['required', 'string', 'min:8', 'confirmed'],
'url'      => ['nullable', 'url', 'active_url'],

// Numeric rules
'age'      => ['required', 'integer', 'between:18,100'],
'price'    => ['required', 'numeric', 'min:0', 'max:9999.99'],
'quantity' => ['required', 'digits_between:1,5'],

// Date rules
'start_date' => ['required', 'date', 'date_format:Y-m-d'],
'end_date'   => ['required', 'date', 'after:start_date'],

// File rules
'avatar'   => ['required', 'image', 'mimes:jpg,png,gif,webp', 'max:1024'],
'document' => ['required', 'file', 'mimes:pdf,doc,docx', 'max:5120'],

// Database rules
'category_id' => ['required', 'exists:categories,id'],
'username'    => ['required', 'unique:users,username,' . $userId],  // ignore own record

// Array rules
'tags'   => ['required', 'array', 'min:1', 'max:5'],
'tags.*' => ['integer', 'exists:tags,id'],

// Conditional rules
'company' => ['required_if:account_type,business'],
'vat'     => ['required_unless:country,US'],
'phone'   => ['required_with:country_code'],

// Custom Rule object
'code' => ['required', new \App\Rules\ValidPromoCode],
Blade CRUD Form: A complete create/edit Blade form using @csrf, @method('PUT') for updates, @error directives for inline validation errors, and old() to repopulate fields.
📄resources/views/posts/create.blade.php
HTML
@extends('layouts.app')
@section('content')

<h1>{{ isset($post) ? 'Edit Post' : 'Create Post' }}</h1>

<form action="{{ isset($post) ? route('posts.update', $post) : route('posts.store') }}"
      method="POST" enctype="multipart/form-data">
    @csrf
    @isset($post)
        @method('PUT')
    @endisset

    {{-- Title --}}
    <div class="mb-3">
        <label for="title">Title</label>
        <input type="text" id="title" name="title"
               value="{{ old('title', $post->title ?? '') }}"
               class="form-control @error('title') is-invalid @enderror">
        @error('title')
            <div class="invalid-feedback">{{ $message }}</div>
        @enderror
    </div>

    {{-- Body --}}
    <div class="mb-3">
        <label for="body">Content</label>
        <textarea id="body" name="body" rows="8"
                  class="form-control @error('body') is-invalid @enderror">{{ old('body', $post->body ?? '') }}</textarea>
        @error('body')
            <div class="invalid-feedback">{{ $message }}</div>
        @enderror
    </div>

    {{-- Status Select --}}
    <div class="mb-3">
        <label for="status">Status</label>
        <select id="status" name="status"
                class="form-select @error('status') is-invalid @enderror">
            <option value="draft"     {{ old('status', $post->status ?? '') === 'draft'     ? 'selected' : '' }}>Draft</option>
            <option value="published" {{ old('status', $post->status ?? '') === 'published' ? 'selected' : '' }}>Published</option>
        </select>
        @error('status')
            <div class="invalid-feedback">{{ $message }}</div>
        @enderror
    </div>

    {{-- Image --}}
    <div class="mb-3">
        <label for="image">Cover Image</label>
        <input type="file" id="image" name="image"
               class="form-control @error('image') is-invalid @enderror">
        @error('image')
            <div class="invalid-feedback">{{ $message }}</div>
        @enderror
    </div>

    <button type="submit" class="btn btn-primary">
        {{ isset($post) ? 'Update Post' : 'Create Post' }}
    </button>
</form>

@endsection
Delete with Soft Deletes: Implement safe deletion using SoftDeletes trait so records are hidden but kept in the database. Include a restore mechanism.
📄app/Models/Post.php
PHP
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Factories\HasFactory;

class Post extends Model
{
    use HasFactory, SoftDeletes;

    protected $fillable = ['title', 'slug', 'body', 'status', 'image', 'published_at'];

    protected $casts = [
        'published_at' => 'datetime',
    ];

    // Scope: only published posts
    public function scopePublished($query)
    {
        return $query->where('status', 'published')->whereNotNull('published_at');
    }
}

// Migration: add soft-deletes column
// Schema::table('posts', function (Blueprint $table) {
//     $table->softDeletes(); // adds deleted_at column
// });

// In controller – soft delete (sets deleted_at)
$post->delete();

// Query including trashed records
Post::withTrashed()->get();

// Restore a soft-deleted record
Post::withTrashed()->find($id)->restore();

// Permanently delete
Post::withTrashed()->find($id)->forceDelete();

// Blade: delete form (POST tunnel for DELETE method)
// <form action="{{ route('posts.destroy', $post) }}" method="POST">
//     @csrf  @method('DELETE')
//     <button>Delete</button>
// </form>