DEV Community

Cover image for How to Build a Generic CRUD Controller in Laravel for Multiple Resources
Abdullah Qasim
Abdullah Qasim

Posted on

How to Build a Generic CRUD Controller in Laravel for Multiple Resources

How to Build a Generic CRUD Controller in Laravel for Multiple Resources

Managing multiple CRUD operations in a Laravel application can be overwhelming, especially when handling a growing number of models. In this post, I'll guide you on creating a generic CRUD controller that allows you to handle existing and future CRUD operations in a single controller.

Why Use a Generic Controller?

A generic controller helps:

  • Minimize repetitive code.
  • Make adding new models a breeze.
  • Provide a consistent structure for your application.

Let’s dive into the implementation step by step!


Step 1: Setting Up the Controller

Start by creating a new controller:

php artisan make:controller GenericController
Enter fullscreen mode Exit fullscreen mode

Step 2: Writing the Controller Logic

Here’s how you can design your GenericController to handle CRUD operations for any model:

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Str;

class GenericController extends Controller
{
    protected function getModel($modelName)
    {
        $modelClass = 'App\\Models\\' . Str::studly($modelName);

        if (!class_exists($modelClass)) {
            abort(404, "Model $modelName not found.");
        }

        return new $modelClass;
    }

    public function index($model)
    {
        $modelInstance = $this->getModel($model);
        return response()->json($modelInstance::all());
    }

    public function show($model, $id)
    {
        $modelInstance = $this->getModel($model);
        return response()->json($modelInstance::findOrFail($id));
    }

    public function store(Request $request, $model)
    {
        $modelInstance = $this->getModel($model);
        $data = $request->validate($modelInstance->getFillable());
        $created = $modelInstance::create($data);

        return response()->json($created, 201);
    }

    public function update(Request $request, $model, $id)
    {
        $modelInstance = $this->getModel($model);
        $item = $modelInstance::findOrFail($id);
        $data = $request->validate($modelInstance->getFillable());
        $item->update($data);

        return response()->json($item);
    }

    public function destroy($model, $id)
    {
        $modelInstance = $this->getModel($model);
        $item = $modelInstance::findOrFail($id);
        $item->delete();

        return response()->json(['message' => 'Deleted successfully.']);
    }
}
Enter fullscreen mode Exit fullscreen mode

Step 3: Dynamic Routing

Configure your routes to use dynamic endpoints:

use App\Http\Controllers\GenericController;

Route::controller(GenericController::class)->prefix('api/{model}')->group(function () {
    Route::get('/', 'index'); 
    Route::get('/{id}', 'show'); 
    Route::post('/', 'store'); 
    Route::put('/{id}', 'update'); 
    Route::delete('/{id}', 'destroy'); 
});
Enter fullscreen mode Exit fullscreen mode

Step 4: Prepare Your Models

Ensure each model has:

  • A $fillable property to specify mass-assignable fields.

Example for a Post model:

namespace App\Models;

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

class Post extends Model
{
    use HasFactory;

    protected $fillable = ['title', 'content'];
}

Enter fullscreen mode Exit fullscreen mode

Key Advantages

  • Scalability: Easily handle new models by adding only the model file.
  • Code Reusability: Reduces redundancy.
  • Simplifies Maintenance: Focus on business logic without worrying about boilerplate code.

When to Use This Approach?

This is ideal for:

  • Applications with standard CRUD logic.
  • Projects where models share common behavior.

For more complex business logic, you may still need dedicated controllers.

Top comments (0)