Monday

18-08-2025 Vol 19

Build CRUD App Laravel 12: Complete Step-by-Step Guide

Build CRUD App Laravel 12: Complete Step-by-Step Guide

This comprehensive guide will walk you through building a complete CRUD (Create, Read, Update, Delete) application using Laravel 12, the latest version of the popular PHP framework. Whether you’re a beginner looking to learn Laravel or an experienced developer wanting to brush up on your skills, this tutorial will provide you with a clear and concise roadmap. We’ll cover everything from setting up your development environment to implementing robust CRUD operations with proper validation and error handling.

Table of Contents

  1. Introduction to CRUD Applications and Laravel 12
  2. Prerequisites
  3. Setting up Your Development Environment
    1. Install Composer
    2. Install Laravel 12
    3. Database Setup
  4. Creating the Model and Migration
    1. Generating the Model
    2. Creating the Migration
    3. Defining the Migration Schema
    4. Running the Migration
  5. Building the Controller
    1. Generating the Controller
    2. Implementing the Index Method (Read)
    3. Implementing the Create Method (Display Create Form)
    4. Implementing the Store Method (Create)
    5. Implementing the Show Method (Read Single Record)
    6. Implementing the Edit Method (Display Edit Form)
    7. Implementing the Update Method (Update)
    8. Implementing the Destroy Method (Delete)
  6. Creating the Views
    1. Index View (Listing Records)
    2. Create View (Create Form)
    3. Show View (Display Single Record)
    4. Edit View (Edit Form)
  7. Defining the Routes
    1. Using Resource Routes
    2. Defining Routes Manually
  8. Implementing Validation
    1. Defining Validation Rules
    2. Using Form Requests
    3. Displaying Validation Errors
  9. Error Handling and Exception Handling
  10. Implementing Authentication (Optional)
  11. Implementing Authorization (Optional)
  12. Testing Your CRUD Application
  13. Conclusion

Introduction to CRUD Applications and Laravel 12

CRUD stands for Create, Read, Update, and Delete. These are the four basic operations that can be performed on data in a database. A CRUD application provides an interface for users to perform these operations, typically through a web application.

Laravel 12, the latest iteration of the framework, provides a robust and elegant environment for building CRUD applications. Its features include:

  • Eloquent ORM: Simplifies database interactions.
  • Blade Templating Engine: Provides a clean and expressive syntax for creating views.
  • Artisan Console: Offers powerful commands for scaffolding and managing your application.
  • Routing System: Enables you to define routes and map them to controller actions.
  • Validation: Provides a simple and convenient way to validate user input.

Prerequisites

Before you begin, ensure you have the following installed:

  • PHP 8.1 or higher: Laravel 12 requires PHP 8.1 or greater.
  • Composer: A dependency manager for PHP.
  • Database Server: MySQL, PostgreSQL, SQLite, or SQL Server.
  • Web Server: Apache or Nginx.

Setting up Your Development Environment

Install Composer

If you don’t have Composer installed, download and install it from the official Composer website.

Install Laravel 12

Open your terminal and navigate to the directory where you want to create your project. Then, use the Composer command to install a new Laravel 12 project:

composer create-project laravel/laravel crud-app

Replace `crud-app` with your desired project name. This command will download Laravel 12 and all its dependencies.

Database Setup

Create a new database for your application. You can use a tool like phpMyAdmin, Dbeaver, or the command line to create the database.

Next, configure your database connection in the `.env` file located in the root of your project. Update the following variables with your database credentials:

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=your_database_name
DB_USERNAME=your_database_username
DB_PASSWORD=your_database_password

Replace `your_database_name`, `your_database_username`, and `your_database_password` with your actual database credentials.

Creating the Model and Migration

Let’s create a model and migration for our data. For this example, we’ll create a simple `Product` model.

Generating the Model

Use the Artisan command to generate a new model:

php artisan make:model Product

This command will create a `Product.php` file in the `app/Models` directory.

Creating the Migration

We’ll also create a migration to define the database table structure. You can create a migration along with the model using the `-m` flag:

php artisan make:model Product -m

This command will create a migration file in the `database/migrations` directory. The filename will be similar to `YYYY_MM_DD_HHMMSS_create_products_table.php`.

Defining the Migration Schema

Open the migration file and define the schema for the `products` table. For example, let’s add fields for `name`, `description`, and `price`:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('products', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->text('description')->nullable();
            $table->decimal('price', 8, 2);
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('products');
    }
};

This code defines a `products` table with the following columns:

  • `id`: The primary key (auto-incrementing integer).
  • `name`: A string for the product name.
  • `description`: A text field for the product description (nullable).
  • `price`: A decimal field for the product price.
  • `timestamps`: Adds `created_at` and `updated_at` columns.

Running the Migration

Run the migration to create the table in your database:

php artisan migrate

This command will execute all pending migrations, including the one you just created.

Building the Controller

Now, let’s create a controller to handle the CRUD operations for our `Product` model.

Generating the Controller

Use the Artisan command to generate a new controller:

php artisan make:controller ProductController --resource

The `–resource` flag tells Artisan to generate a resource controller with methods for handling all CRUD operations. This will create `ProductController.php` in the `app/Http/Controllers` directory.

Implementing the Index Method (Read)

The `index` method is responsible for displaying a list of all products. Open `ProductController.php` and add the following code to the `index` method:

<?php

namespace App\Http\Controllers;

use App\Models\Product;
use Illuminate\Http\Request;

class ProductController extends Controller
{
    /**
     * Display a listing of the resource.
     */
    public function index()
    {
        $products = Product::all();
        return view('products.index', compact('products'));
    }

    // ... other methods ...
}

This code retrieves all products from the database using the `Product::all()` method and passes them to the `products.index` view.

Implementing the Create Method (Display Create Form)

The `create` method is responsible for displaying the form for creating a new product. Add the following code to the `create` method:

   /**
     * Show the form for creating a new resource.
     */
    public function create()
    {
        return view('products.create');
    }

This code simply returns the `products.create` view.

Implementing the Store Method (Create)

The `store` method is responsible for creating a new product in the database. Add the following code to the `store` method:

   /**
     * Store a newly created resource in storage.
     */
    public function store(Request $request)
    {
        $request->validate([
            'name' => 'required|string|max:255',
            'description' => 'nullable|string',
            'price' => 'required|numeric|min:0',
        ]);

        Product::create($request->all());

        return redirect()->route('products.index')
                         ->with('success', 'Product created successfully.');
    }

This code first validates the request data using the `validate` method. Then, it creates a new product using the `Product::create()` method and redirects the user to the `products.index` route with a success message.

Implementing the Show Method (Read Single Record)

The `show` method is responsible for displaying a single product. Add the following code to the `show` method:

   /**
     * Display the specified resource.
     */
    public function show(Product $product)
    {
        return view('products.show', compact('product'));
    }

This code retrieves the product with the given ID using route model binding and passes it to the `products.show` view.

Implementing the Edit Method (Display Edit Form)

The `edit` method is responsible for displaying the form for editing an existing product. Add the following code to the `edit` method:

   /**
     * Show the form for editing the specified resource.
     */
    public function edit(Product $product)
    {
        return view('products.edit', compact('product'));
    }

This code retrieves the product with the given ID using route model binding and passes it to the `products.edit` view.

Implementing the Update Method (Update)

The `update` method is responsible for updating an existing product in the database. Add the following code to the `update` method:

   /**
     * Update the specified resource in storage.
     */
    public function update(Request $request, Product $product)
    {
        $request->validate([
            'name' => 'required|string|max:255',
            'description' => 'nullable|string',
            'price' => 'required|numeric|min:0',
        ]);

        $product->update($request->all());

        return redirect()->route('products.index')
                         ->with('success', 'Product updated successfully.');
    }

This code first validates the request data using the `validate` method. Then, it updates the product using the `update` method and redirects the user to the `products.index` route with a success message.

Implementing the Destroy Method (Delete)

The `destroy` method is responsible for deleting a product from the database. Add the following code to the `destroy` method:

   /**
     * Remove the specified resource from storage.
     */
    public function destroy(Product $product)
    {
        $product->delete();

        return redirect()->route('products.index')
                         ->with('success', 'Product deleted successfully.');
    }

This code deletes the product using the `delete` method and redirects the user to the `products.index` route with a success message.

Creating the Views

Now, let’s create the views for displaying the data and forms. Create a `products` directory inside the `resources/views` directory.

Index View (Listing Records)

Create a file named `index.blade.php` inside the `resources/views/products` directory and add the following code:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Products</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="container">
    <h1>Products</h1>

    <a href="{{ route('products.create') }}" class="btn btn-primary mb-3">Create New Product</a>

    @if (session('success'))
        <div class="alert alert-success">
            {{ session('success') }}
        </div>
    @endif

    <table class="table">
        <thead>
            <tr>
                <th>ID</th>
                <th>Name</th>
                <th>Price</th>
                <th>Actions</th>
            </tr>
        </thead>
        <tbody>
            @foreach ($products as $product)
                <tr>
                    <td>{{ $product->id }}</td>
                    <td>{{ $product->name }}</td>
                    <td>{{ $product->price }}</td>
                    <td>
                        <a href="{{ route('products.show', $product->id) }}" class="btn btn-sm btn-info">View</a>
                        <a href="{{ route('products.edit', $product->id) }}" class="btn btn-sm btn-primary">Edit</a>
                        <form action="{{ route('products.destroy', $product->id) }}" method="POST" style="display: inline;">
                            @csrf
                            @method('DELETE')
                            <button type="submit" class="btn btn-sm btn-danger" onclick="return confirm('Are you sure?')">Delete</button>
                        </form>
                    </td>
                </tr>
            @endforeach
        </tbody>
    </table>
</body>
</html>

This view displays a table of all products with links to view, edit, and delete each product. It also displays a success message if one is present in the session. The code uses Bootstrap for basic styling. Make sure to include Bootstrap CSS in your project. You can use a CDN like in the example, or install Bootstrap using npm or yarn.

Create View (Create Form)

Create a file named `create.blade.php` inside the `resources/views/products` directory and add the following code:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Create Product</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="container">
    <h1>Create Product</h1>

    <form action="{{ route('products.store') }}" method="POST">
        @csrf

        <div class="mb-3">
            <label for="name" class="form-label">Name</label>
            <input type="text" class="form-control" id="name" name="name" value="{{ old('name') }}" required>
        </div>

        <div class="mb-3">
            <label for="description" class="form-label">Description</label>
            <textarea class="form-control" id="description" name="description" rows="3">{{ old('description') }}</textarea>
        </div>

        <div class="mb-3">
            <label for="price" class="form-label">Price</label>
            <input type="number" step="0.01" class="form-control" id="price" name="price" value="{{ old('price') }}" required>
        </div>

        <button type="submit" class="btn btn-primary">Create</button>
        <a href="{{ route('products.index') }}" class="btn btn-secondary">Cancel</a>
    </form>

    @if ($errors->any())
        <div class="alert alert-danger mt-3">
            <ul>
                @foreach ($errors->all() as $error)
                    <li>{{ $error }}</li>
                @endforeach
            </ul>
        </div>
    @endif
</body>
</html>

This view displays a form for creating a new product. It includes fields for `name`, `description`, and `price`. It also displays validation errors if any are present. The `old()` helper function is used to repopulate the form fields with the previously entered values if validation fails. This provides a better user experience. Again, Bootstrap CSS is used for styling.

Show View (Display Single Record)

Create a file named `show.blade.php` inside the `resources/views/products` directory and add the following code:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Product Details</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="container">
    <h1>Product Details</h1>

    <div class="card">
        <div class="card-body">
            <h5 class="card-title">{{ $product->name }}</h5>
            <p class="card-text">{{ $product->description }}</p>
            <p class="card-text"><strong>Price:</strong> {{ $product->price }}</p>
            <a href="{{ route('products.index') }}" class="btn btn-primary">Back to Products</a>
        </div>
    </div>
</body>
</html>

This view displays the details of a single product. It shows the product’s name, description, and price. It also includes a link back to the product listing page. The layout utilizes a Bootstrap card for visual structure.

Edit View (Edit Form)

Create a file named `edit.blade.php` inside the `resources/views/products` directory and add the following code:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Edit Product</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="container">
    <h1>Edit Product</h1>

    <form action="{{ route('products.update', $product->id) }}" method="POST">
        @csrf
        @method('PUT')

        <div class="mb-3">
            <label for="name" class="form-label">Name</label>
            <input type="text" class="form-control" id="name" name="name" value="{{ old('name', $product->name) }}" required>
        </div>

        <div class="mb-3">
            <label for="description" class="form-label">Description</label>
            <textarea class="form-control" id="description" name="description" rows="3">{{ old('description', $product->description) }}</textarea>
        </div>

        <div class="mb-3">
            <label for="price" class="form-label">Price</label>
            <input type="number" step="0.01" class="form-control" id="price" name="price" value="{{ old('price', $product->price) }}" required>
        </div>

        <button type="submit" class="btn btn-primary">Update</button>
        <a href="{{ route('products.index') }}" class="btn btn-secondary">Cancel</a>
    </form>

    @if ($errors->any())
        <div class="alert alert-danger mt-3">
            <ul>
                @foreach ($errors->all() as $error)
                    <li>{{ $error }}</li>
                @endforeach
            </ul>
        </div>
    @endif
</body>
</html>

This view displays a form for editing an existing product. It includes fields for `name`, `description`, and `price`, pre-populated with the current product data. It also displays validation errors if any are present. The `@method(‘PUT’)` directive is used to spoof a PUT request, as HTML forms only support GET and POST. The `old()` helper is used to prioritize displaying previously entered (and potentially valid) values over the database values in case of validation failure.

Defining the Routes

Now, let’s define the routes for our CRUD operations.

Using Resource Routes

Laravel provides a convenient way to define routes for resource controllers using the `resource` method in the `routes/web.php` file. Add the following line to the file:

Route::resource('products', ProductController::class);

This single line of code defines all the necessary routes for the CRUD operations on the `products` resource. It automatically maps the following routes:

  • `GET /products`: `ProductController@index` (Display a list of all products)
  • `GET /products/create`: `ProductController@create` (Display the form for creating a new product)
  • `POST /products`: `ProductController@store` (Create a new product)
  • `GET /products/{product}`: `ProductController@show` (Display a single product)
  • `GET /products/{product}/edit`: `ProductController@edit` (Display the form for editing an existing product)
  • `PUT/PATCH /products/{product}`: `ProductController@update` (Update an existing product)
  • `DELETE /products/{product}`: `ProductController@destroy` (Delete a product)

Defining Routes Manually

Alternatively, you can define the routes manually if you need more control over the route names or middleware. For example:

Route::get('/products', [ProductController::class, 'index'])->name('products.index');
Route::get('/products/create', [ProductController::class, 'create'])->name('products.create');
Route::post('/products', [ProductController::class, 'store'])->name('products.store');
Route::get('/products/{product}', [ProductController::class, 'show'])->name('products.show');
Route::get('/products/{product}/edit', [ProductController::class, 'edit'])->name('products.edit');
Route::put('/products/{product}', [ProductController::class, 'update'])->name('products.update');
Route::delete('/products/{product}', [ProductController::class, 'destroy'])->name('products.destroy');

This code defines each route individually and assigns a name to each route using the `name()` method. This allows you to easily generate URLs to these routes using the `route()` helper function in your views and controllers.

Implementing Validation

Validating user input is crucial for ensuring data integrity and preventing security vulnerabilities.

Defining Validation Rules

We’ve already seen how to use the `validate()` method in the controller to define validation rules. The `validate()` method takes an array of rules as its argument. Here are some common validation rules:

  • `required`: The field is required.
  • `string`: The field must be a string.
  • `numeric`: The field must be a number.
  • `integer`: The field must be an integer.
  • `email`: The field must be a valid email address.
  • `min:value`: The field must have a minimum value.
  • `max:value`: The field must have a maximum value.
  • `unique:table,column`: The field must be unique in the specified table and column.

Using Form Requests

For more complex validation scenarios, you can use Form Requests. Form Requests are classes that encapsulate the validation logic for a specific request.

To create a Form Request, use the Artisan command:

php artisan make:request StoreProductRequest

This command will create a `StoreProductRequest.php` file in the `app/Http/Requests` directory. Open the file and define the authorization and rules methods:

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class StoreProductRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return true; // Or implement your authorization logic
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array|string>
     */
    public function rules(): array
    {
        return [
            'name' => 'required|string|max:255',
            'description' => 'nullable|string',
            'price' => 'required|numeric|min:0',
        ];
    }
}

The `authorize()` method determines if the user is authorized to make the request. By default, it returns `true`. You can implement your own authorization logic here.

The `rules()` method returns an array of validation rules. This is the same array you would pass to the `validate()` method in the controller.

To use the Form Request in your controller, type-hint it in the `store` and `update` methods:

public

omcoding

Leave a Reply

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