BLOG POSTS
Simple Laravel CRUD with Resource Controllers

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.

Leave a reply

Your email address will not be published. Required fields are marked