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.

Image of Timescale

🚀 pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applications—without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

Read full post →

Top comments (0)

Image of Docusign

🛠️ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay