
Simple Laravel CRUD with Resource Controllers
Laravel’s resource controllers are one of those features that make you wonder how you ever built web applications without them. They provide a clean, standardized way to handle CRUD (Create, Read, Update, Delete) operations by following RESTful conventions, which means less boilerplate code and more consistent application structure. Whether you’re spinning up a quick prototype on a VPS or deploying to production dedicated servers, mastering resource controllers will streamline your Laravel development workflow and make your code more maintainable.
How Laravel Resource Controllers Work
Resource controllers in Laravel automatically map HTTP verbs to controller methods following RESTful conventions. Instead of manually defining routes for each CRUD operation, Laravel generates seven standard methods that handle common resource operations:
HTTP Method | URI | Controller Method | Purpose |
---|---|---|---|
GET | /posts | index() | Display all resources |
GET | /posts/create | create() | Show form to create new resource |
POST | /posts | store() | Store new resource |
GET | /posts/{id} | show() | Display specific resource |
GET | /posts/{id}/edit | edit() | Show form to edit resource |
PUT/PATCH | /posts/{id} | update() | Update specific resource |
DELETE | /posts/{id} | destroy() | Remove specific resource |
This approach eliminates the guesswork around naming conventions and URL structures. When you register a resource route, Laravel automatically creates all seven routes with proper HTTP methods and meaningful names.
Step-by-Step Implementation Guide
Let’s build a complete CRUD system for managing blog posts. First, generate a resource controller using Artisan:
php artisan make:controller PostController --resource
This creates a controller with all seven resource methods stubbed out. Here’s what the generated controller looks like:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class PostController extends Controller
{
public function index()
{
//
}
public function create()
{
//
}
public function store(Request $request)
{
//
}
public function show($id)
{
//
}
public function edit($id)
{
//
}
public function update(Request $request, $id)
{
//
}
public function destroy($id)
{
//
}
}
Next, create a Post model and migration:
php artisan make:model Post -m
Define your migration schema in the generated migration file:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreatePostsTable extends Migration
{
public function up()
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('content');
$table->string('slug')->unique();
$table->boolean('published')->default(false);
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('posts');
}
}
Run the migration:
php artisan migrate
Now register the resource route in your routes/web.php file:
Route::resource('posts', PostController::class);
This single line creates all seven routes. You can verify this by running:
php artisan route:list --name=posts
Complete CRUD Implementation
Let’s implement each method in the PostController. First, update your Post model to include fillable fields:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
class Post extends Model
{
protected $fillable = ['title', 'content', 'slug', 'published'];
protected static function boot()
{
parent::boot();
static::creating(function ($post) {
if (empty($post->slug)) {
$post->slug = Str::slug($post->title);
}
});
}
}
Now implement the controller methods:
<?php
namespace App\Http\Controllers;
use App\Models\Post;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
class PostController extends Controller
{
public function index()
{
$posts = Post::latest()->paginate(10);
return view('posts.index', compact('posts'));
}
public function create()
{
return view('posts.create');
}
public function store(Request $request)
{
$validated = $request->validate([
'title' => 'required|max:255',
'content' => 'required',
'published' => 'boolean'
]);
$post = Post::create($validated);
return redirect()->route('posts.show', $post)
->with('success', 'Post created successfully!');
}
public function show(Post $post)
{
return view('posts.show', compact('post'));
}
public function edit(Post $post)
{
return view('posts.edit', compact('post'));
}
public function update(Request $request, Post $post)
{
$validated = $request->validate([
'title' => 'required|max:255',
'content' => 'required',
'published' => 'boolean'
]);
$post->update($validated);
return redirect()->route('posts.show', $post)
->with('success', 'Post updated successfully!');
}
public function destroy(Post $post)
{
$post->delete();
return redirect()->route('posts.index')
->with('success', 'Post deleted successfully!');
}
}
Notice how we’re using route model binding by type-hinting the Post model in method parameters. Laravel automatically resolves the model instance based on the route parameter.
Creating the Views
Create a basic layout file resources/views/layouts/app.blade.php:
<!DOCTYPE html>
<html>
<head>
<title>Laravel CRUD</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-4">
@if(session('success'))
<div class="alert alert-success">{{ session('success') }}</div>
@endif
@yield('content')
</div>
</body>
</html>
Create the index view resources/views/posts/index.blade.php:
@extends('layouts.app')
@section('content')
<div class="d-flex justify-content-between align-items-center mb-4">
<h1>Posts</h1>
<a href="{{ route('posts.create') }}" class="btn btn-primary">Create New Post</a>
</div>
<div class="row">
@forelse($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>
<div class="btn-group">
<a href="{{ route('posts.show', $post) }}" class="btn btn-sm btn-outline-primary">View</a>
<a href="{{ route('posts.edit', $post) }}" class="btn btn-sm btn-outline-secondary">Edit</a>
<form action="{{ route('posts.destroy', $post) }}" method="POST" class="d-inline">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-sm btn-outline-danger"
onclick="return confirm('Are you sure?')">Delete</button>
</form>
</div>
</div>
</div>
</div>
@empty
<p>No posts found.</p>
@endforelse
</div>
{{ $posts->links() }}
@endsection
Performance Considerations and Best Practices
Resource controllers shine when you follow these optimization patterns:
- Use route model binding: Let Laravel automatically resolve models instead of manual queries
- Implement pagination: Always paginate large datasets in your index method
- Add proper validation: Use Form Request classes for complex validation rules
- Cache frequently accessed data: Cache your index queries when dealing with high traffic
- Use eager loading: Prevent N+1 queries when displaying related models
Here’s an optimized version of the index method with caching:
public function index()
{
$posts = Cache::remember('posts.index', 300, function () {
return Post::with('user')
->published()
->latest()
->paginate(10);
});
return view('posts.index', compact('posts'));
}
Common Pitfalls and Troubleshooting
Here are the most frequent issues developers encounter with resource controllers:
- Missing CSRF tokens: Always include @csrf directive in forms
- Method spoofing confusion: Use @method(‘PUT’) or @method(‘DELETE’) for update and destroy operations
- Route parameter mismatch: Ensure your route parameter names match your controller method parameters
- Mass assignment vulnerabilities: Always define $fillable or $guarded properties in your models
- Validation bypass: Don’t forget to validate input in both store() and update() methods
If you’re getting “Method not allowed” errors, check that your forms are using the correct HTTP methods:
<!-- Correct form for updates -->
<form action="{{ route('posts.update', $post) }}" method="POST">
@csrf
@method('PUT')
<!-- form fields -->
</form>
Advanced Resource Controller Features
Laravel provides several ways to customize resource controllers beyond the basic implementation:
Limiting Resource Routes:
// Only create certain routes
Route::resource('posts', PostController::class)->only(['index', 'show']);
// Exclude specific routes
Route::resource('posts', PostController::class)->except(['destroy']);
Nested Resources:
Route::resource('posts.comments', CommentController::class);
API Resources:
// Excludes create and edit methods (no forms needed for APIs)
Route::apiResource('posts', PostController::class);
Custom Route Names:
Route::resource('posts', PostController::class)->names([
'create' => 'posts.build',
'show' => 'posts.view'
]);
Real-World Use Cases
Resource controllers work exceptionally well for these scenarios:
- Content Management Systems: Blog posts, pages, media files
- E-commerce Platforms: Products, categories, orders, customers
- User Management: Admin panels, profile management, role assignments
- API Development: RESTful APIs for mobile apps or SPA backends
- Inventory Systems: Stock management, suppliers, purchase orders
For high-traffic applications running on dedicated infrastructure, consider implementing caching strategies and database optimization. Resource controllers make it easy to add caching layers without disrupting your application logic.
Comparison with Alternative Approaches
Feature | Resource Controllers | Manual Route Definition | Single Action Controllers |
---|---|---|---|
Code Organization | Excellent | Fair | Good |
Convention Consistency | Excellent | Poor | Fair |
Development Speed | Fast | Slow | Medium |
Flexibility | Good | Excellent | Excellent |
Learning Curve | Low | Medium | Low |
Resource controllers strike the perfect balance between convention and flexibility. While manual route definition offers more control, resource controllers handle 80% of use cases with significantly less code. For complex business logic that doesn’t fit RESTful patterns, you can always mix resource controllers with custom routes.
The official Laravel documentation provides comprehensive coverage of resource controllers and their advanced features at https://laravel.com/docs/controllers#resource-controllers. For additional learning resources, the Laravel community maintains excellent tutorials and examples at https://laracasts.com.

This article incorporates information and material from various online sources. We acknowledge and appreciate the work of all original authors, publishers, and websites. While every effort has been made to appropriately credit the source material, any unintentional oversight or omission does not constitute a copyright infringement. All trademarks, logos, and images mentioned are the property of their respective owners. If you believe that any content used in this article infringes upon your copyright, please contact us immediately for review and prompt action.
This article is intended for informational and educational purposes only and does not infringe on the rights of the copyright owners. If any copyrighted material has been used without proper credit or in violation of copyright laws, it is unintentional and we will rectify it promptly upon notification. Please note that the republishing, redistribution, or reproduction of part or all of the contents in any form is prohibited without express written permission from the author and website owner. For permissions or further inquiries, please contact us.