When I first started building Laravel applications, CRUD seemed like a collection of unrelated files and commands. Once I understood how routes, controllers, models, views, and databases communicate, the entire process became much easier.
In this guide, I will explain how to create CRUD in Laravel through a practical product-management application. The finished project will let users create, view, edit, and delete products while applying validation, pagination, security, and clean coding practices.
What Does CRUD Mean in Laravel?
CRUD represents the four basic actions used to manage database records:
- Create adds a new record.
- Read retrieves existing records.
- Update changes a stored record.
- Delete removes a record.
Laravel simplifies these operations through Eloquent ORM, resource controllers, resource routes, migrations, and Blade templates. A browser request reaches a route, the route calls a controller method, the controller works with an Eloquent model, and a Blade view displays the response.
What You Need Before Starting
Make sure your development environment includes:
- A compatible PHP version
- Composer
- Laravel
- MySQL, MariaDB, PostgreSQL, or SQLite
- A code editor
- Basic PHP knowledge
Check the requirements of the Laravel version you install because supported PHP versions can change between releases.
Step 1: Create a Laravel Project

Open a terminal and create a fresh project:
composer create-project laravel/laravel product-manager
cd product-manager
Start the local development server:
php artisan serve
Open the displayed local address in your browser. You should see Laravel’s welcome page.
Step 2: Configure the Database
Create a database called product_manager, then update the database values in the .env file:
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=product_manager
DB_USERNAME=root
DB_PASSWORD=
Use credentials that match your database environment. Run php artisan config:clear if Laravel continues using older configuration values.
Step 3: Generate the Model and Migration

Generate a Product model with a migration:
php artisan make:model Product -m
Open the new migration inside database/migrations and define the products table:
Schema::create(‘products’, function (Blueprint $table) {
$table->id();
$table->string(‘name’);
$table->string(‘sku’)->unique();
$table->unsignedInteger(‘quantity’)->default(0);
$table->decimal(‘price’, 10, 2);
$table->text(‘description’)->nullable();
$table->timestamps();
});
Run the migration:
php artisan migrate
The database now contains a structured products table.
Step 4: Configure the Product Model
Open app/Models/Product.php and declare the attributes that can be mass assigned:
protected $fillable = [
‘name’,
‘sku’,
‘quantity’,
‘price’,
‘description’,
];
The $fillable property prevents unexpected fields from being written through mass-assignment operations.
Step 5: Create Form Request Validation

Generate separate request classes for storing and updating records:
php artisan make:request StoreProductRequest
php artisan make:request UpdateProductRequest
Add suitable rules to each class:
public function rules(): array
{
return [
‘name’ => [‘required’, ‘string’, ‘max:255’],
‘sku’ => [‘required’, ‘string’, ‘max:100’, ‘unique:products,sku’],
‘quantity’ => [‘required’, ‘integer’, ‘min:0’],
‘price’ => [‘required’, ‘numeric’, ‘min:0’],
‘description’ => [‘nullable’, ‘string’],
];
}
Adjust the SKU rule in the update request so it ignores the product currently being edited. This allows the existing SKU to remain unchanged while still blocking duplicates belonging to other products.
Step 6: Generate a Resource Controller
Create a controller containing the standard resource methods:
php artisan make:controller ProductController –resource
The controller should contain these actions:
Index
Retrieve products and send them to the list page:
$products = Product::latest()->paginate(10);
return view(‘products.index’, compact(‘products’));
Store
Validate and create a product:
Product::create($request->validated());
return redirect()
->route(‘products.index’)
->with(‘success’, ‘Product created successfully.’);
Update
Validate and update the selected record:
$product->update($request->validated());
return redirect()
->route(‘products.index’)
->with(‘success’, ‘Product updated successfully.’);
Destroy
Delete the selected product:
$product->delete();
return redirect()
->route(‘products.index’)
->with(‘success’, ‘Product deleted successfully.’);
Use route model binding by type-hinting Product $product in methods that work with an existing record.
Step 7: Register the Resource Route

Open routes/web.php and add:
use App\Http\Controllers\ProductController;
Route::resource(‘products’, ProductController::class);
This single declaration registers routes for listing, creating, storing, viewing, editing, updating, and deleting products.
Check the generated routes with:
php artisan route:list
Step 8: Create the Blade Views
Create a products directory inside resources/views. Add these files:
- index.blade.php
- create.blade.php
- show.blade.php
- edit.blade.php
The index page should display the product list, pagination links, success messages, and actions for viewing, editing, and deleting records.
Every form that changes data must include:
@csrf
Update forms also need:
@method(‘PUT’)
Delete forms require:
@method(‘DELETE’)
Display field errors near their inputs and use Laravel’s old() helper to restore submitted values after validation fails. Add a browser confirmation before deletion to reduce accidental removals.
Step 9: Add Search and Pagination
Search makes the application more useful as its database grows. Update the index query so it checks the name or SKU when a search term exists, then paginate the result.
Keep query parameters while moving between pages so the search does not disappear. A clear empty-state message should also explain when no matching products are available.
Step 10: Test the Application
Test the complete workflow manually:
- Create a valid product.
- Submit an incomplete form.
- Try a duplicate SKU.
- Open the details page.
- Edit the product.
- Delete the product.
- Confirm pagination works.
Feature tests should verify that pages load, valid records are stored, invalid data is rejected, records are updated, and deleted products disappear from the database.
Common Laravel CRUD Problems

A “table not found” error usually means the migration has not run or the application is connected to the wrong database.
A mass-assignment error commonly means the model is missing its $fillable configuration.
A route-not-defined message may indicate an incorrect route name or a cached route configuration.
A method-not-allowed error often happens when update and delete forms do not include the correct method directive.
Laravel CRUD Security Practices
Use server-side validation for every submitted form. Keep CSRF protection enabled, escape displayed values through standard Blade syntax, restrict mass assignment, and add authorization before allowing sensitive changes.
Database security also depends on keeping user input separate from executable queries, so understanding how to prevent SQL injection in PHP is useful when building or reviewing data-driven Laravel applications.
Authentication confirms who a user is, while authorization determines what that user is permitted to do. Public-facing applications should apply policies or gates instead of allowing every authenticated user to edit every record.
Frequently Asked Questions
1. How long does it take to learn how to create CRUD in Laravel?
A basic application can be completed quickly, but understanding validation, routing, Eloquent, security, and testing requires additional practice.
2. What is a Laravel resource controller?
A resource controller organizes the conventional actions needed to create, display, edit, update, and delete a particular application resource.
3. Can Laravel CRUD work without Blade?
Yes. Laravel can provide CRUD through JSON APIs, Livewire components, Inertia applications, React interfaces, Vue interfaces, or administration packages.
4. Why should I use Form Request classes?
Form Request classes keep validation and authorization logic outside controllers, making the application easier to maintain, test, and expand.
From Basic Records to Better Applications
Building this project helped me see CRUD as a connected request lifecycle rather than a set of commands to memorize. The route receives the request, the controller coordinates the work, the model communicates with the database, and the view presents the result.
As the frontend grows more complex, developers may also explore how to convert JavaScript to TypeScript to add stronger type checking and make larger application codebases easier to maintain.
I recommend improving the finished project with authentication, authorization, sorting, filters, reusable form components, automated tests, and database backups. Once these foundations are clear, the same approach can support customer directories, inventory systems, task managers, content platforms, and many other applications.







































