Building a CRUD app Laravel 12 is one of the best ways to learn web development fundamentals. CRUD stands for Create, Read, Update, and Delete - the four basic operations you need in most web applications.
In this guide, I’ll walk you through building a complete CRUD Laravel 12 application from scratch. You’ll learn to create a simple blog post management system that handles all CRUD operations.
What is CRUD in Laravel 12?
CRUD represents the four essential database operations:
- Create: Add new records
- Read: Display existing records
- Update: Modify existing records
- Delete: Remove records
Laravel 12 makes building CRUD applications incredibly simple with its built-in features like Eloquent ORM, Blade templates, and resourceful routes.
Prerequisites
Before we start building our CRUD Laravel 12 app, make sure you have:
- PHP 8.2 or higher
- Composer installed
- Laravel 12 installed
- MySQL or SQLite database
- Basic understanding of PHP and HTML
Step 1: Create a New Laravel 12 Project
First, let’s create a fresh Laravel 12 project for our CRUD application:
composer create-project laravel/laravel crud-laravel-12
cd crud-laravel-12
This command creates a new Laravel 12 project specifically for our CRUD app Laravel 12.
Step 2: Set Up Database Configuration
Open your .env
file and configure your database settings:
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=crud_laravel_12
DB_USERNAME=your_username
DB_PASSWORD=your_password
Create the database named crud_laravel_12
in your MySQL server.
Step 3: Create Migration for Posts Table
Let’s create a migration for our blog posts table. This is essential for any CRUD Laravel 12 application:
php artisan make:migration create_posts_table
Open the migration file in database/migrations/
and add the following structure:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up()
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('content');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('posts');
}
};
Run the migration:
php artisan migrate
Step 4: Create the Post Model
Generate a model for our CRUD Laravel 12 application:
php artisan make:model Post
Open app/Models/Post.php
and add the fillable fields:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
protected $fillable = [
'title',
'content',
];
}
Step 5: Create the Posts Controller
Create a resourceful controller that handles all CRUD operations:
php artisan make:controller PostController --resource
Open app/Http/Controllers/PostController.php
and implement the CRUD methods:
<?php
namespace App\Http\Controllers;
use App\Models\Post;
use Illuminate\Http\Request;
class PostController extends Controller
{
// Display all posts (READ)
public function index()
{
$posts = Post::latest()->paginate(10);
return view('posts.index', compact('posts'));
}
// Show form to create new post
public function create()
{
return view('posts.create');
}
// Store new post (CREATE)
public function store(Request $request)
{
$request->validate([
'title' => 'required|max:255',
'content' => 'required',
]);
Post::create($request->all());
return redirect()->route('posts.index')
->with('success', 'Post created successfully.');
}
// Display single post
public function show(Post $post)
{
return view('posts.show', compact('post'));
}
// Show form to edit post
public function edit(Post $post)
{
return view('posts.edit', compact('post'));
}
// Update existing post (UPDATE)
public function update(Request $request, Post $post)
{
$request->validate([
'title' => 'required|max:255',
'content' => 'required',
]);
$post->update($request->all());
return redirect()->route('posts.index')
->with('success', 'Post updated successfully.');
}
// Delete post (DELETE)
public function destroy(Post $post)
{
$post->delete();
return redirect()->route('posts.index')
->with('success', 'Post deleted successfully.');
}
}
Step 6: Set Up Routes
Add resourceful routes to routes/web.php
:
<?php
use App\Http\Controllers\PostController;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return redirect()->route('posts.index');
});
Route::resource('posts', PostController::class);
This single line creates all necessary routes for our CRUD Laravel 12 application:
- GET
/posts
- List all posts - GET
/posts/create
- Show create form - POST
/posts
- Store new post - GET
/posts/{post}
- Show single post - GET
/posts/{post}/edit
- Show edit form - PUT
/posts/{post}
- Update post - DELETE
/posts/{post}
- Delete post
Step 7: Create Blade Templates
Create Layout Template
First, create a main layout at resources/views/layouts/app.blade.php
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>@yield('title', 'CRUD Laravel 12 App')</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
<h1 class="mb-4">Laravel 12 CRUD Application</h1>
@if(session('success'))
<div class="alert alert-success">
{{ session('success') }}
</div>
@endif
@yield('content')
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>
Create Posts Index View
Create resources/views/posts/index.blade.php
:
@extends('layouts.app')
@section('title', 'All Posts - CRUD Laravel 12')
@section('content')
<div class="d-flex justify-content-between align-items-center mb-4">
<h2>All Posts</h2>
<a href="{{ route('posts.create') }}" class="btn btn-primary">Create New Post</a>
</div>
@if($posts->count() > 0)
<div class="row">
@foreach($posts as $post)
<div class="col-md-6 mb-4">
<div class="card">
<div class="card-body">
<h5 class="card-title">{{ $post->title }}</h5>
<p class="card-text">{{ Str::limit($post->content, 100) }}</p>
<small class="text-muted">{{ $post->created_at->format('M d, Y') }}</small>
<div class="mt-3">
<a href="{{ route('posts.show', $post) }}" class="btn btn-info btn-sm">View</a>
<a href="{{ route('posts.edit', $post) }}" class="btn btn-warning btn-sm">Edit</a>
<form action="{{ route('posts.destroy', $post) }}" method="POST" class="d-inline">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-danger btn-sm"
onclick="return confirm('Are you sure?')">Delete</button>
</form>
</div>
</div>
</div>
</div>
@endforeach
</div>
{{ $posts->links() }}
@else
<div class="alert alert-info">
No posts found. <a href="{{ route('posts.create') }}">Create your first post</a>
</div>
@endif
@endsection
Create Post Creation Form
Create resources/views/posts/create.blade.php
:
@extends('layouts.app')
@section('title', 'Create Post - CRUD Laravel 12')
@section('content')
<h2>Create New Post</h2>
<form action="{{ route('posts.store') }}" method="POST">
@csrf
<div class="mb-3">
<label for="title" class="form-label">Title</label>
<input type="text" class="form-control @error('title') is-invalid @enderror"
id="title" name="title" value="{{ old('title') }}" required>
@error('title')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="mb-3">
<label for="content" class="form-label">Content</label>
<textarea class="form-control @error('content') is-invalid @enderror"
id="content" name="content" rows="5" required>{{ old('content') }}</textarea>
@error('content')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<button type="submit" class="btn btn-success">Create Post</button>
<a href="{{ route('posts.index') }}" class="btn btn-secondary">Cancel</a>
</form>
@endsection
Create Post Edit Form
Create resources/views/posts/edit.blade.php
:
@extends('layouts.app')
@section('title', 'Edit Post - CRUD Laravel 12')
@section('content')
<h2>Edit Post</h2>
<form action="{{ route('posts.update', $post) }}" method="POST">
@csrf
@method('PUT')
<div class="mb-3">
<label for="title" class="form-label">Title</label>
<input type="text" class="form-control @error('title') is-invalid @enderror"
id="title" name="title" value="{{ old('title', $post->title) }}" required>
@error('title')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="mb-3">
<label for="content" class="form-label">Content</label>
<textarea class="form-control @error('content') is-invalid @enderror"
id="content" name="content" rows="5" required>{{ old('content', $post->content) }}</textarea>
@error('content')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<button type="submit" class="btn btn-success">Update Post</button>
<a href="{{ route('posts.index') }}" class="btn btn-secondary">Cancel</a>
</form>
@endsection
Create Post Detail View
Create resources/views/posts/show.blade.php
:
@extends('layouts.app')
@section('title', $post->title . ' - CRUD Laravel 12')
@section('content')
<div class="card">
<div class="card-body">
<h1 class="card-title">{{ $post->title }}</h1>
<small class="text-muted">Published on {{ $post->created_at->format('M d, Y') }}</small>
<div class="mt-4">
<p class="card-text">{{ $post->content }}</p>
</div>
<div class="mt-4">
<a href="{{ route('posts.edit', $post) }}" class="btn btn-warning">Edit Post</a>
<a href="{{ route('posts.index') }}" class="btn btn-secondary">Back to Posts</a>
<form action="{{ route('posts.destroy', $post) }}" method="POST" class="d-inline">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-danger"
onclick="return confirm('Are you sure you want to delete this post?')">
Delete Post
</button>
</form>
</div>
</div>
</div>
@endsection
Step 8: Test Your CRUD Laravel 12 Application
Start your Laravel development server:
php artisan serve
Visit http://localhost:8000
to see your CRUD Laravel 12 application in action.
Key Features of Our CRUD Laravel 12 App
1. Create Functionality
Users can add new blog posts through a simple form with title and content fields.
2. Read Functionality
The app displays all posts in a paginated list and shows individual post details.
3. Update Functionality
Users can edit existing posts while preserving the original data in the form.
4. Delete Functionality
Posts can be safely deleted with a confirmation dialog.
Best Practices for CRUD Laravel 12 Applications
1. Form Validation
Always validate user input to ensure data integrity:
$request->validate([
'title' => 'required|max:255',
'content' => 'required|min:10',
]);
2. Use Route Model Binding
Laravel automatically resolves model instances based on route parameters:
public function show(Post $post)
{
return view('posts.show', compact('post'));
}
3. Implement Pagination
For better performance with large datasets:
$posts = Post::latest()->paginate(10);
4. Add CSRF Protection
Always include CSRF tokens in your forms:
@csrf
5. Handle Soft Deletes
For important data, consider using soft deletes instead of permanent deletion.
Common Issues and Solutions
Issue 1: Route Not Found
Solution: Make sure you’ve registered the resource routes correctly:
Route::resource('posts', PostController::class);
Issue 2: Mass Assignment Error
Solution: Add fields to the $fillable
array in your model:
protected $fillable = ['title', 'content'];
Issue 3: Validation Errors Not Showing
Solution: Include error display in your Blade templates:
@error('title')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
Advanced CRUD Features
Adding Search Functionality
Enhance your CRUD Laravel 12 app with search capabilities:
public function index(Request $request)
{
$query = Post::query();
if ($request->has('search')) {
$query->where('title', 'like', '%' . $request->search . '%')
->orWhere('content', 'like', '%' . $request->search . '%');
}
$posts = $query->latest()->paginate(10);
return view('posts.index', compact('posts'));
}
Adding Categories
Extend your CRUD app with relationships:
// In Post model
public function category()
{
return $this->belongsTo(Category::class);
}
Performance Optimization Tips
1. Use Eager Loading
Prevent N+1 query problems:
$posts = Post::with('category')->latest()->paginate(10);
2. Add Database Indexes
For frequently searched columns:
$table->index('title');
3. Cache Frequently Accessed Data
Use Laravel’s caching system for better performance:
$posts = Cache::remember('posts', 3600, function () {
return Post::latest()->take(10)->get();
});
Security Considerations
1. Authorization
Implement proper user authorization:
public function edit(Post $post)
{
$this->authorize('update', $post);
return view('posts.edit', compact('post'));
}
2. Input Sanitization
Always validate and sanitize user input to prevent XSS attacks.
3. Rate Limiting
Protect your CRUD endpoints from abuse:
Route::middleware('throttle:60,1')->group(function () {
Route::resource('posts', PostController::class);
});
Testing Your CRUD Laravel 12 Application
Feature Tests
Create tests for your CRUD operations:
public function test_user_can_create_post()
{
$response = $this->post('/posts', [
'title' => 'Test Post',
'content' => 'Test content'
]);
$response->assertRedirect('/posts');
$this->assertDatabaseHas('posts', [
'title' => 'Test Post'
]);
}
Conclusion
Building a CRUD app Laravel 12 is straightforward when you understand the basic components: models, controllers, routes, and views. This guide covered everything you need to create a fully functional CRUD application.
The CRUD Laravel 12 approach we’ve used here follows Laravel’s conventions and best practices. You can extend this foundation to build more complex applications by adding features like user authentication, file uploads, and API endpoints.
Remember that successful CRUD Laravel 12 applications focus on user experience, security, and maintainable code. Start with this simple example and gradually add more features as your skills improve.
Next Steps
Now that you’ve built your first CRUD Laravel 12 application, consider:
- Adding user authentication with Laravel Breeze
- Implementing file upload functionality
- Creating API endpoints for mobile apps
- Adding real-time features with Laravel Broadcasting
- Deploying your app to production
Happy coding with Laravel 12 CRUD applications!