Simple Laravel 12 CRUD Application Tutorial


Laravel 12 CRUD Application

Download

In this tutorial, I will show you how to create a Laravel 12 CRUD application by developing a complete system with a step-by-step guide.

CRUD stands for Create, Read, Update, and Delete — the four basic operations used in managing data in persistent storage.

I will create a simple products table and store product details in five columns: code (VARCHAR), name (VARCHAR), quantity (INT), price (DECIMAL), and description (TEXT).

The following screenshots show the Laravel 12 CRUD application, which can store, view, update, and delete products from the products table.

Product List Page

Product List Page

Add New Product Page

Add New Product Page

Edit Product Page

Edit Product Page

Show Product Page

Show Product Page

Readers Also Read: Laravel 12 Custom User Registration and Login Tutorial

Now, let’s begin building a Laravel 12 CRUD application with a straightforward example using a products table.

Steps to Create Laravel 12 CRUD Application

Follow the step-by-step guide below to create a Laravel 12 CRUD application.

  1. Install and Set Up Laravel 12 App
  2. Create a Model with Migration, Resource Controller and Requests for Validation
  3. Update Product Migration & Migrate Tables to Database
  4. Define Product Resource Routes
  5. Update Code in Product Model
  6. Update Code in Product Controller
  7. Update Code in Product Store and Update Requests
  8. Enable Bootstrap 5 in AppServiceProvider
  9. Create Layout and Product Resource Blade View Files
  10. Run Laravel Development Server

Step 1. Install and Set Up Laravel 12 App

If you don’t have Laravel 12 installed, start by creating a new Laravel 12 application named crud_app using the following command:

composer create-project --prefer-dist laravel/laravel crud_app

Now, navigate to the crud_app directory using the following command.

cd crud_app

By default, Laravel 12 uses SQLite. However, in this tutorial, we will use MySQL.

First, create a database named crud_app, and then configure your database credentials in the .env file as shown below:

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=crud_app
DB_USERNAME=your_db_username
DB_PASSWORD=your_db_password

Step 2. Create a Model with Migration, Resource Controller and Requests for Validation

In this step, we need to create a Model, Migration, Resource Controller, and Form Request for validating the products table.

Although we can create each of them individually, Laravel also allows us to generate them all at once using a single Artisan command.

Simply run the following command in your terminal:

php artisan make:model Product -mcr --requests

In the above command, the -m flag generates a migration for the model, the -cr flag creates a resource controller, and the --requests flag generates custom form request classes for the resource controller.

Step 3. Update Product Migration & Migrate Tables to Database

Now, we need to update the product migration file.

Navigate to the crud_app/database/migrations directory, where you will find the product migration file named something like:

YYYY_MM_DD_TIMESTAMP_create_products_table.php

Open this file and replace its content with the following code:

<?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('code')->unique();
            $table->string('name');
            $table->integer('quantity');
            $table->decimal('price', 8, 2);
            $table->text('description')->nullable();
            $table->timestamps();
        });
    }

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

Once the product migration file is updated, we need to migrate all tables into our database.

Run the following Artisan command in the terminal to perform the migration:

php artisan migrate

Step 4. Define Product Resource Routes

In this step, we need to define the product resource routes for our application in the web.php file.

Simply copy and paste the following code into your routes/web.php file:

<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ProductController;

Route::get('/', function () {
    return view('welcome');
});
Route::resource('products', ProductController::class);

After defining the application routes, you can verify them by running the following Artisan command in the terminal:

php artisan route:list

Step 5. Update Code in Product Model

Now, we need to allow mass assignment in the Product model.

Navigate to app/Models/Product.php and update the file with the following code:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    protected $fillable = [
        'code',
        'name',
        'quantity',
        'price',
        'description'
    ];
}

Step 6. Update Code in Product Controller

In this step, we need to update our ProductController, which includes seven different methods to perform Laravel Eloquent CRUD operations in our Laravel 12 CRUD application.

By default, a resource controller comes with the following methods, and we will use all of them in our Laravel 12 CRUD app:

  1. index() – Displays a list of all products from the products table.
  2. create() – Shows the form to add a new product.
  3. store() – Handles the submission of the new product form and stores the data in the products table.
  4. show() – Displays the details of a single product.
  5. edit() – Shows the form to edit an existing product.
  6. update() – Updates the product data in the products table.
  7. destroy() – Deletes a product from the database.

Now, copy and paste the following code into the app/Http/Controllers/ProductController.php file.

<?php

namespace App\Http\Controllers;

use App\Models\Product;
use App\Http\Requests\StoreProductRequest;
use App\Http\Requests\UpdateProductRequest;
use Illuminate\View\View;
use Illuminate\Http\RedirectResponse;

class ProductController extends Controller
{
    /**
     * Display a listing of the resource.
     */
    public function index() : View
    {
        return view('products.index', [
            'products' => Product::latest()->paginate(3)
        ]);
    }

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

    /**
     * Store a newly created resource in storage.
     */
    public function store(StoreProductRequest $request) : RedirectResponse
    {
        Product::create($request->validated());

        return redirect()->route('products.index')
                ->withSuccess('New product is added successfully.');
    }

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

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

    /**
     * Update the specified resource in storage.
     */
    public function update(UpdateProductRequest $request, Product $product) : RedirectResponse
    {
        $product->update($request->validated());

        return redirect()->back()
                ->withSuccess('Product is updated successfully.');
    }

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

        return redirect()->route('products.index')
                ->withSuccess('Product is deleted successfully.');
    }
}

Step 7. Update Code in Product Store and Update Requests

In this step, we will update the code in our product store and update request classes.

First, copy and paste the following code into the app/Http/Requests/StoreProductRequest.php file.

<?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;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            'code' => 'required|string|max:50|unique:products,code',
            'name' => 'required|string|max:250',
            'quantity' => 'required|integer|min:1|max:10000',
            'price' => 'required',
            'description' => 'nullable|string'
        ];
    }
}

After that, copy and paste the following code into the app/Http/Requests/UpdateProductRequest.php file.

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

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

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            'code' => 'required|string|max:50|unique:products,code,'.$this->product->id,
            'name' => 'required|string|max:250',
            'quantity' => 'required|integer|min:1|max:10000',
            'price' => 'required',
            'description' => 'nullable|string'
        ];
    }
}

Both of these form request files are responsible for validating the data before adding or updating records in the products table of our database.

Step 8. Enable Bootstrap 5 in AppServiceProvider

Since we are using Bootstrap v5.3.7 via CDN, some of its features—such as pagination—won’t work properly unless explicitly enabled in the App\Providers\AppServiceProvider.php file.

To fix this, we need to call Paginator::useBootstrapFive(); inside the boot() method.

Simply copy and paste the following code into your AppServiceProvider.php file:

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Pagination\Paginator;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     */
    public function register(): void
    {
        //
    }

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        Paginator::useBootstrapFive();
    }
}

Step 9. Create Layout and Product Resource Blade View Files

In this step, we need to create /layouts and /products directories inside the resources/views/ directory, and then create the necessary Blade view files within them.

These files can be created manually or by using Artisan commands. In this tutorial, we’ll use Artisan to generate them.

Simply run the following Artisan commands, and all five Blade view files will be created in their respective directories:

php artisan make:view layouts.app
php artisan make:view products.index
php artisan make:view products.create
php artisan make:view products.edit
php artisan make:view products.show

The above commands will generate the following Blade view files:

  1. app.blade.php
  2. index.blade.php
  3. create.blade.php
  4. edit.blade.php
  5. show.blade.php

Now, we need to update each of these view files with the appropriate code.

Starting with app.blade.php, which serves as the main layout view file for our Laravel 12 CRUD application:

Copy and paste the following code into resources/views/layouts/app.blade.php:

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Simple Laravel 12 CRUD Application Tutorial - AllPHPTricks.com</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" crossorigin="anonymous">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.css" crossorigin="anonymous">
</head>
<body>   

    <div class="container">
        <h3 class=" mt-3">Simple Laravel 12 CRUD Application Tutorial - <a href="https://www.allphptricks.com/">AllPHPTricks.com</a></h3>
            @yield('content')
            <div class="row justify-content-center text-center mt-3">
                <div class="col-md-12">
                    <p>Back to Tutorial: 
                        <a href="https://www.allphptricks.com/simple-laravel-12-crud-application-tutorial/"><strong>Tutorial Link</strong></a>
                    </p>
                    <p>
                        For More Web Development Tutorials Visit: <a href="https://www.allphptricks.com/"><strong>AllPHPTricks.com</strong></a>
                    </p>
                </div>
            </div>
    </div>

<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
</body>
</html>

index.blade.php is the main landing page of the Laravel 12 CRUD application. It displays a list of all products from the database along with pagination.

Copy and paste the following code into the resources/views/products/index.blade.php file:

@extends('layouts.app')

@section('content')

<div class="row justify-content-center mt-3">
    <div class="col-md-12">

        @session('success')
            <div class="alert alert-success" role="alert">
                {{ $value }}
            </div>
        @endsession

        <div class="card">
            <div class="card-header">Product List</div>
            <div class="card-body">
                <a href="{{ route('products.create') }}" class="btn btn-success btn-sm my-2"><i class="bi bi-plus-circle"></i> Add New Product</a>
                <table class="table table-striped table-bordered">
                    <thead>
                      <tr>
                        <th scope="col">S#</th>
                        <th scope="col">Code</th>
                        <th scope="col">Name</th>
                        <th scope="col">Quantity</th>
                        <th scope="col">Price</th>
                        <th scope="col">Action</th>
                      </tr>
                    </thead>
                    <tbody>
                        @forelse ($products as $product)
                        <tr>
                            <th scope="row">{{ $loop->iteration }}</th>
                            <td>{{ $product->code }}</td>
                            <td>{{ $product->name }}</td>
                            <td>{{ $product->quantity }}</td>
                            <td>{{ $product->price }}</td>
                            <td>
                                <form action="{{ route('products.destroy', $product->id) }}" method="post">
                                    @csrf
                                    @method('DELETE')

                                    <a href="{{ route('products.show', $product->id) }}" class="btn btn-warning btn-sm"><i class="bi bi-eye"></i> Show</a>

                                    <a href="{{ route('products.edit', $product->id) }}" class="btn btn-primary btn-sm"><i class="bi bi-pencil-square"></i> Edit</a>   

                                    <button type="submit" class="btn btn-danger btn-sm" onclick="return confirm('Do you want to delete this product?');"><i class="bi bi-trash"></i> Delete</button>
                                </form>
                            </td>
                        </tr>
                        @empty
                            <td colspan="6">
                                <span class="text-danger">
                                    <strong>No Product Found!</strong>
                                </span>
                            </td>
                        @endforelse
                    </tbody>
                  </table>

                  {{ $products->links() }}

            </div>
        </div>
    </div>    
</div>
    
@endsection

create.blade.php is the Blade view file used to add a new product.

Simply copy and paste the following code into resources/views/products/create.blade.php:

@extends('layouts.app')

@section('content')

<div class="row justify-content-center mt-3">
    <div class="col-md-8">

        <div class="card">
            <div class="card-header">
                <div class="float-start">
                    Add New Product
                </div>
                <div class="float-end">
                    <a href="{{ route('products.index') }}" class="btn btn-primary btn-sm">&larr; Back</a>
                </div>
            </div>
            <div class="card-body">
                <form action="{{ route('products.store') }}" method="post">
                    @csrf

                    <div class="mb-3 row">
                        <label for="code" class="col-md-4 col-form-label text-md-end text-start">Code</label>
                        <div class="col-md-6">
                          <input type="text" class="form-control @error('code') is-invalid @enderror" id="code" name="code" value="{{ old('code') }}">
                            @error('code')
                                <span class="text-danger">{{ $message }}</span>
                            @enderror
                        </div>
                    </div>

                    <div class="mb-3 row">
                        <label for="name" class="col-md-4 col-form-label text-md-end text-start">Name</label>
                        <div class="col-md-6">
                          <input type="text" class="form-control @error('name') is-invalid @enderror" id="name" name="name" value="{{ old('name') }}">
                            @error('name')
                                <span class="text-danger">{{ $message }}</span>
                            @enderror
                        </div>
                    </div>

                    <div class="mb-3 row">
                        <label for="quantity" class="col-md-4 col-form-label text-md-end text-start">Quantity</label>
                        <div class="col-md-6">
                          <input type="number" class="form-control @error('quantity') is-invalid @enderror" id="quantity" name="quantity" value="{{ old('quantity') }}">
                            @error('quantity')
                                <span class="text-danger">{{ $message }}</span>
                            @enderror
                        </div>
                    </div>

                    <div class="mb-3 row">
                        <label for="price" class="col-md-4 col-form-label text-md-end text-start">Price</label>
                        <div class="col-md-6">
                          <input type="number" step="0.01" class="form-control @error('price') is-invalid @enderror" id="price" name="price" value="{{ old('price') }}">
                            @error('price')
                                <span class="text-danger">{{ $message }}</span>
                            @enderror
                        </div>
                    </div>

                    <div class="mb-3 row">
                        <label for="description" class="col-md-4 col-form-label text-md-end text-start">Description</label>
                        <div class="col-md-6">
                            <textarea class="form-control @error('description') is-invalid @enderror" id="description" name="description">{{ old('description') }}</textarea>
                            @error('description')
                                <span class="text-danger">{{ $message }}</span>
                            @enderror
                        </div>
                    </div>
                    
                    <div class="mb-3 row">
                        <input type="submit" class="col-md-3 offset-md-5 btn btn-primary" value="Add Product">
                    </div>
                    
                </form>
            </div>
        </div>
    </div>    
</div>
    
@endsection

edit.blade.php is the Blade view file used for editing a product.

Simply copy and paste the following code into resources/views/products/edit.blade.php:

@extends('layouts.app')

@section('content')

<div class="row justify-content-center mt-3">
    <div class="col-md-8">

        @session('success')
            <div class="alert alert-success" role="alert">
                {{ $value }}
            </div>
        @endsession

        <div class="card">
            <div class="card-header">
                <div class="float-start">
                    Edit Product
                </div>
                <div class="float-end">
                    <a href="{{ route('products.index') }}" class="btn btn-primary btn-sm">&larr; Back</a>
                </div>
            </div>
            <div class="card-body">
                <form action="{{ route('products.update', $product->id) }}" method="post">
                    @csrf
                    @method("PUT")

                    <div class="mb-3 row">
                        <label for="code" class="col-md-4 col-form-label text-md-end text-start">Code</label>
                        <div class="col-md-6">
                          <input type="text" class="form-control @error('code') is-invalid @enderror" id="code" name="code" value="{{ $product->code }}">
                            @error('code')
                                <span class="text-danger">{{ $message }}</span>
                            @enderror
                        </div>
                    </div>

                    <div class="mb-3 row">
                        <label for="name" class="col-md-4 col-form-label text-md-end text-start">Name</label>
                        <div class="col-md-6">
                          <input type="text" class="form-control @error('name') is-invalid @enderror" id="name" name="name" value="{{ $product->name }}">
                            @error('name')
                                <span class="text-danger">{{ $message }}</span>
                            @enderror
                        </div>
                    </div>

                    <div class="mb-3 row">
                        <label for="quantity" class="col-md-4 col-form-label text-md-end text-start">Quantity</label>
                        <div class="col-md-6">
                          <input type="number" class="form-control @error('quantity') is-invalid @enderror" id="quantity" name="quantity" value="{{ $product->quantity }}">
                            @error('quantity')
                                <span class="text-danger">{{ $message }}</span>
                            @enderror
                        </div>
                    </div>

                    <div class="mb-3 row">
                        <label for="price" class="col-md-4 col-form-label text-md-end text-start">Price</label>
                        <div class="col-md-6">
                          <input type="number" step="0.01" class="form-control @error('price') is-invalid @enderror" id="price" name="price" value="{{ $product->price }}">
                            @error('price')
                                <span class="text-danger">{{ $message }}</span>
                            @enderror
                        </div>
                    </div>

                    <div class="mb-3 row">
                        <label for="description" class="col-md-4 col-form-label text-md-end text-start">Description</label>
                        <div class="col-md-6">
                            <textarea class="form-control @error('description') is-invalid @enderror" id="description" name="description">{{ $product->description }}</textarea>
                            @error('description')
                                <span class="text-danger">{{ $message }}</span>
                            @enderror
                        </div>
                    </div>
                    
                    <div class="mb-3 row">
                        <input type="submit" class="col-md-3 offset-md-5 btn btn-primary" value="Update">
                    </div>
                    
                </form>
            </div>
        </div>
    </div>    
</div>
    
@endsection

show.blade.php is the Blade view file used to display a single product’s details.

Simply copy and paste the following code into resources/views/products/show.blade.php:

@extends('layouts.app')

@section('content')

<div class="row justify-content-center mt-3">
    <div class="col-md-8">

        <div class="card">
            <div class="card-header">
                <div class="float-start">
                    Product Information
                </div>
                <div class="float-end">
                    <a href="{{ route('products.index') }}" class="btn btn-primary btn-sm">&larr; Back</a>
                </div>
            </div>
            <div class="card-body">

                    <div class="row">
                        <label for="code" class="col-md-4 col-form-label text-md-end text-start"><strong>Code:</strong></label>
                        <div class="col-md-6" style="line-height: 35px;">
                            {{ $product->code }}
                        </div>
                    </div>

                    <div class="row">
                        <label for="name" class="col-md-4 col-form-label text-md-end text-start"><strong>Name:</strong></label>
                        <div class="col-md-6" style="line-height: 35px;">
                            {{ $product->name }}
                        </div>
                    </div>

                    <div class="row">
                        <label for="quantity" class="col-md-4 col-form-label text-md-end text-start"><strong>Quantity:</strong></label>
                        <div class="col-md-6" style="line-height: 35px;">
                            {{ $product->quantity }}
                        </div>
                    </div>

                    <div class="row">
                        <label for="price" class="col-md-4 col-form-label text-md-end text-start"><strong>Price:</strong></label>
                        <div class="col-md-6" style="line-height: 35px;">
                            {{ $product->price }}
                        </div>
                    </div>

                    <div class="row">
                        <label for="description" class="col-md-4 col-form-label text-md-end text-start"><strong>Description:</strong></label>
                        <div class="col-md-6" style="line-height: 35px;">
                            {{ $product->description }}
                        </div>
                    </div>
        
            </div>
        </div>
    </div>    
</div>
    
@endsection

Step 10. Run Laravel Development Server

Finally, we’ve completed all the steps of our Laravel 12 CRUD application tutorial. Now it’s time to test the application.

Start the Laravel development server by running the following Artisan command:

php artisan serve

After starting the development server, visit the following URL to test your Laravel 12 CRUD application:

http://127.0.0.1:8000/products

Download

Conclusion

We hope that by following the steps above, you have learned how to easily create a simple Laravel 12 CRUD application.

If you found this tutorial helpful, please share it with your friends and developer groups.

I spent several hours creating this tutorial. If you’d like to say thanks, consider liking my pages on Facebook, Twitter, and GitHub — and don’t forget to share it!

Facebook Official Page: All PHP Tricks

Twitter Official Page: All PHP Tricks

Article By
Javed Ur Rehman is a passionate blogger and web developer, he loves to share web development tutorials and blogging tips. He usually writes about HTML, CSS, JavaScript, Jquery, Ajax, PHP and MySQL.

Leave a Reply

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