Published on

A simple CRUD App development experiment using LARAVEL 11

In this experiment, we will explore Laravel 11 which will soon be released. According to Laravel Documentation, Laravel 11 is scheduled to be released in Q1 of 2024 now. And if we try googling about Laravel 11, there are already many articles that discuss what's new in Laravel 11, such as drop support for PHP 8.1, minimalist application skeleton and others. Therefore, here I will write something different, which is a hands-on experiment of developing a simple CRUD App using Laravel 11.

There is a reason why trying to directly develop CRUD features instead of trying to review what's new in Laravel 11, which is to answer the anxiety of people who are just learning Laravel. Because the development framework feels fast, so those who are just learning usually feel that they have just learned one version of Laravel, eh, a new version of Laravel has come out again. So here we will try to use the codebase from the previous version of Laravel tutorials. Hopefully after following this tutorial can answer the anxiety of friends who are just learning.

Okay, let's get started!

Table of Contents

Overview

In this experiment, we will develop a Simple CRUD application using Laravel 11. Here is a list of features that we will add in the Laravel 11 project:

  1. User list view feature
  2. Feature to add a new user
  3. Feature to edit user data
  4. Feature to delete user data

In developing this CRUD application, we will try to reuse the codebase from the previous Laravel tutorial. So that after finishing developing this CRUD feature, we can know whether the previous Laravel codebase can still be used or not.

Preparation

Based on some references that I read, the minimum PHP that we can use is PHP 8.2. So now we check the PHP version first. We open the terminal and run the following command.

php -v

The output after we run the command:

PHP 8.2.15 (cli) (built: Jan 20 2024 14:17:05) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.2.15, Copyright (c) Zend Technologies
    with Zend OPcache v8.2.15, Copyright (c), by Zend Technologies

If the output is not PHP 8.2, you can follow the tutorial setup and use multiple versions of PHP so that you can switch PHP versions easily.

Step 1 - Create Laravel Project

Now let's try to create a new laravel project using composer. Open the terminal again and run the following command to create a new laravel 11 project.

composer create-project --prefer-dist laravel/laravel crud-app-example dev-master

After the create project process is complete, the following output is displayed.

84 packages you are using are looking for funding.
Use the `composer fund` command to find out more!
> @php artisan vendor:publish --tag=laravel-assets --ansi --force

   INFO  No publishable resources for tag [laravel-assets].  

> @php artisan key:generate --ansi

   INFO  Application key set successfully.  

> @php -r "file_exists('database/database.sqlite') || touch('database/database.sqlite');"
> @php artisan migrate --ansi

   INFO  Preparing database.  

  Creating migration table ....................................... 9.14ms DONE

   INFO  Running migrations.  

  0001_01_01_000000_create_users_table .......................... 20.65ms DONE
  0001_01_01_000001_create_cache_table ........................... 6.03ms DONE
  0001_01_01_000002_create_jobs_table ........................... 16.75ms DONE

As we can see above, there is a new sql database creation.

@php -r "file_exists('database/database.sqlite') || touch('database/database.sqlite');"

And if we open the .env file, we can see that the default database used by Laravel 11 is sqlite.

DB_CONNECTION=sqlite

In addition to the above output, there is the following output.

@php artisan migrate --ansi

   INFO  Preparing database.  

  Creating migration table ....................................... 9.14ms DONE

   INFO  Running migrations.  

  0001_01_01_000000_create_users_table .......................... 20.65ms DONE
  0001_01_01_000001_create_cache_table ........................... 6.03ms DONE
  0001_01_01_000002_create_jobs_table ........................... 16.75ms DONE

We can see from the output above, the artisan migrate command is executed immediately after creating a new laravel 11 project.

Since the database is already available and the migrate command is already running, we will try to continue developing the CRUD feature.

Step 2 - Coding User List View Feature

In the previous step we created a new Laravel 11 project, and now we try to enter the project directory.

cd crud-app-example

After that, we try to create a new controller using the following command.

php artisan make:controller UserController --model=User --resource

Output:

   INFO  Controller [app/Http/Controllers/UserController.php] created successfully.  

Next, open the app/Http/Controllers/UserController.php file, then we modify the index() method to display the user list page.

<?php

// BARIS KODE LAINNYA

class UserController extends Controller
{
    /**
     * Display a listing of the resource.
     */
    public function index()
    {
        $users = User::latest()->paginate(10);
        return view('users.index', compact('users'));
    }


    // BARIS KODE LAINNYA
}

Next we create a new view file resources/views/users/index.blade.php. In the resources/views/users/index.blade.php file, we customize the following lines of code.


<!DOCTYPE html>
<html lang="en">

<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">
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <title>User List - Tutorial CRUD Laravel 11 @ qadrlabs.com</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
</head>

<body>

<div class="container mt-5">
    <div class="row">
        <div class="col-md-12">

            <h4>User List</h4>

            <!-- Notifikasi menggunakan flash session data -->
            @if (session('message'))
                <div class="alert alert-success">
                    {{ session('message') }}
                </div>
            @endif

            <div class="card border-0 shadow rounded">
                <div class="card-body">
                    <a href="{{ route('user.create') }}" class="btn btn-md btn-success mb-3 float-end">New
                        User</a>

                    <table class="table table-bordered mt-1 text-center">
                        <thead>
                        <tr>
                            <th scope="col">Name</th>
                            <th scope="col">Email</th>
                            <th scope="col">Create At</th>
                            <th scope="col">Action</th>
                        </tr>
                        </thead>
                        <tbody>
                        @forelse ($users as $user)
                            <tr>
                                <td>{{ $user->name }}</td>
                                <td>{{ $user->email }}</td>
                                <td>{{ $user->created_at->format('d-m-Y') }}</td>
                                <td>
                                    <form onsubmit="return confirm('Apakah Anda Yakin ?');"
                                          action="{{ route('user.destroy', $user->id) }}" method="POST">
                                        <a href="{{ route('user.edit', $user->id) }}"
                                           class="btn btn-sm btn-primary">EDIT</a>
                                        @csrf
                                        @method('DELETE')
                                        <button type="submit" class="btn btn-sm btn-danger">DELETE</button>
                                    </form>
                                </td>
                            </tr>
                        @empty
                            <tr>
                                <td class="text-center text-mute" colspan="4">Data user tidak tersedia</td>
                            </tr>
                        @endforelse
                        </tbody>
                    </table>

                    {{ $users->links() }}
                </div>
            </div>
        </div>
    </div>
</div>

<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous"></script>

</body>

</html>

In the line of code above, there is {$users->links()}} which functions to display pagination. In addition, we can see the following line of code.

<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">

Because by default, for UI using Tailwind, we need to customize it to use laravel pagination.

Now we customize it first so that the laravel 11 project uses bootstrap. Open the app/Providers/AppServiceProvider.php file. Then we customize it into the following lines of code.

<?php

namespace App\Providers;

use Illuminate\Pagination\Paginator; // tambahkan baris kode ini
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{

    // BARIS KODE LAINNYA

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

As we can see in the line of code above, we are using bootstrap 5 in our Laravel 11 project.

Now we define a new route that will handle the CRUD feature in the Laravel 11 project. Open the routes/web.php file, then we customize it like the following lines of code.

<?php

use Illuminate\Support\Facades\Route;

Route::get('/', [\App\Http\Controllers\UserController::class, 'index']);
Route::resource('user', \App\Http\Controllers\UserController::class);

When finished, save the routes/web.php file again.

From the two route definitions you provided, let's discuss each of them:

  1. Route to Display the Home Page:
Route::get('/', [\App\Http\Controllers\UserController::class, 'index']);

This route sets the root URL ('/') to point to the index() method in UserController. When we run the project later, the index() method in UserController will be called to display the main page.

  1. Resourceful Route for User Management:
Route::resource('user', \App\Http\Controllers\UserController::class);

This route uses the resource() method provided by Laravel to create a complete CRUD route for user entity management. It will automatically handle various CRUD operations like create, read, update, and delete.

This is the list of routes created by Route::resource('user', \App\Http\Controllers\UserController::class):

  • GET /user: Displays a list of all users.
  • GET /user/create: Displays a form to create a new user.
  • POST /user: Saves the new user data.
  • GET /user/{id}: Displays the details of the user with the specified ID.
  • GET /user/{id}/edit: Displays the form to edit the user with the specified ID.
  • PUT/PATCH /user/{id}: Updates the data of the user with the specified ID.
  • DELETE /user/{id}: Deletes the user with the specified ID.

By using Route::resource(), we can get the complete route for user management with little code.

Step 3 - Coding Create User Feature

At this stage we will add a feature to add a new user. Now we reopen the app/Http/Controllers/UserController.php file, then we modify the create() method to display the add new user form page.

<?php

// BARIS KODE LAINNYA

class UserController extends Controller
{
    // BARIS KODE LAINNYA

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

    // BARIS KODE LAINNYA
}


Next we create a new view file resources/views/users/create.blade.php. Now we customize resources/views/users/create.blade.php like the following lines of code.

<!DOCTYPE html>
<html lang="en">

<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">
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <title>Create New User - Tutorial CRUD Laravel 11 @ qadrlabs.com</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
</head>

<body>

<div class="container mt-5 mb-5">
    <div class="row">
        <div class="col-md-12">

            <h4>Create New User</h4>

            <div class="card border-0 shadow rounded">
                <div class="card-body">

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

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

                            <!-- error message untuk name -->
                            @error('name')
                            <div class="invalid-feedback" role="alert">
                                {{ $message }}
                            </div>
                            @enderror
                        </div>

                        <div class="mb-3">
                            <label for="email">Email Address</label>
                            <input type="email" class="form-control @error('email') is-invalid @enderror"
                                   name="email" value="{{ old('email') }}" required>

                            <!-- error message untuk email -->
                            @error('email')
                            <div class="invalid-feedback" role="alert">
                                {{ $message }}
                            </div>
                            @enderror
                        </div>

                        <div class="mb-3">
                            <label for="password">Password</label>
                            <input type="password" class="form-control @error('password') is-invalid @enderror"
                                   name="password" required>

                            <!-- error message untuk password -->
                            @error('password')
                            <div class="invalid-feedback" role="alert">
                                {{ $message }}
                            </div>
                            @enderror
                        </div>

                        <div class="mb-3">
                            <label for="password_confirmation">Confirm Password</label>
                            <input type="password" class="form-control"
                                   name="password_confirmation" required>

                        </div>


                        <button type="submit" class="btn btn-md btn-primary">Save</button>
                        <a href="{{ route('user.index') }}" class="btn btn-md btn-secondary">back</a>

                    </form>
                </div>
            </div>
        </div>
    </div>
</div>

<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous"></script>

</body>

</html>

In the line of code above, there is the following line of code.

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

The action of the add user data form is directed to the store() method, in the UserController controller. Now we modify the store() method to handle the process of adding a new user to the users table.

<?php

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash; // tambahkan ini

class UserController extends Controller
{
    
    // BARIS KODE LAINNYA

    /**
     * Store a newly created resource in storage.
     */
    public function store(Request $request)
    {
        $request->validate([
            'name' => 'required|string|max:255',
            'email' => 'required|string|email|max:255|unique:users',
            'password' => 'required|string|min:8|confirmed'
        ]);

        $user = User::create([
            'name' => $request->name,
            'email' => $request->email,
            'password' => Hash::make($request->password)
        ]);

        return redirect()
            ->route('user.index')
            ->with('message', 'New user created successfully');
    }

    // BARIS KODE LAINNYA
}

Since we use the make() function from Illuminate\Support\Facades\Hash to hash passwords, don't forget to add the use Illuminate\Support\Facades\Hash; statement before the UserController class declaration.

After we finish coding for the store() method, don't forget to save the app/Http/Controllers/UserController.php file again.

Step 4 - Edit User Feature Coding

In this step we add a feature to edit user data.

Open the app/Http/Controllers/UserController.php file again, then we modify the edit() method to display the edit form page.

<?php

// BARIS KODE LAINNYA

class UserController extends Controller
{
    

    // BARIS KODE LAINNYA

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

    // BARIS KODE LAINNYA
}

Next we create a new view file resources/views/users/edit.blade.php. Then we customize it into the following lines of code.

<!DOCTYPE html>
<html lang="en">

<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">
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <title>Edit User - Tutorial CRUD Laravel 11 @ qadrlabs.com</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
</head>

<body>

<div class="container mt-5 mb-5">
    <div class="row">
        <div class="col-md-12">

            <h4>Edit User</h4>

            <div class="card border-0 shadow rounded">
                <div class="card-body">

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

                        <div class="mb-3">
                            <label for="name">Name</label>
                            <input type="text" class="form-control @error('name') is-invalid @enderror"
                                   name="name" value="{{ old('name', $user->name) }}" required>

                            <!-- error message untuk name -->
                            @error('name')
                            <div class="invalid-feedback">
                                {{ $message }}
                            </div>
                            @enderror
                        </div>

                        <div class="mb-3">
                            <label for="email">Email Address</label>
                            <input type="email" class="form-control @error('email') is-invalid @enderror"
                                   name="email" value="{{ old('email', $user->email) }}" required>

                            <!-- error message untuk email -->
                            @error('email')
                            <div class="invalid-feedback">
                                {{ $message }}
                            </div>
                            @enderror
                        </div>

                        <div class="mb-3">
                            <label for="password">Password</label>
                            <input type="password" class="form-control @error('password') is-invalid @enderror"
                                   name="password" value="{{ old('password') }}">

                            <!-- error message untuk password -->
                            @error('password')
                            <div class="invalid-feedback">
                                {{ $message }}
                            </div>
                            @enderror
                        </div>


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

                    </form>
                </div>
            </div>
        </div>
    </div>
</div>


<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous"></script>

</body>

</html>

Since the form action points to the update() method in the controller class UserController, we now modify the update() method to handle the user data update process.

<?php

// BARIS KODE LAINNYA

class UserController extends Controller
{
    
    // BARIS KODE LAINNYA

    /**
     * Update the specified resource in storage.
     */
    public function update(Request $request, User $user)
    {
        $request->validate([
            'name' => 'required|string|max:255',
            'email' => 'required|string|email|max:255|unique:users,'.$user->id,
        ]);

        $user->name = $request->name;
        $user->email = $request->email;

        if (! empty($request->get('password'))) {
            $user->password = Hash::make($request->password);
        }
        $user->save();

        return redirect()
            ->route('user.index')
            ->with('message', 'User updated successfully');
    }

    // BARIS KODE LAINNYA
    
}

When finished, save the app/Http/Controllers/UserController.php file again.

Step 5 - Coding Delete User Feature

The last feature of this Laravel 11 CRUD application is the feature to delete user data.

Now open the app/Http/Controllers/UserController.php file again, and modify the destroy() method that handles the delete user data.

<?php


// BARIS KODE LAINNYA

class UserController extends Controller
{
    
    // BARIS KODE LAINNYA

    /**
     * Remove the specified resource from storage.
     */
    public function destroy(User $user)
    {
        $user->delete();
        return redirect()
            ->route('user.index')
            ->with('message', 'User deleted successfully');
    }
}

When finished save the app/Http/Controllers/UserController.php file again.

Step 6 - Test the Project

We have finished coding the CRUD feature for the Laravel 11 project, to test it we can run the project first.

php artisan serve

The first page displayed is the user list page. Here we can try the features that we have coded such as adding data, editing data, deleting data and displaying data along with pagination.

Conclusion

In this experiment, we have tried to develop a Laravel 11 project with CRUD features for user data. In this experiment, we tried to use the codebase from the previous Laravel tutorial and after we tested the codebase, it still works well. This means that we don't need to worry when we are still learning the previous version of Laravel, because we can still use the knowledge in Laravel 11.

In addition to the codebase, there are differences when we create a new Laravel 11 project using composer, namely the default database used in Laravel 11 is sqlite and we can see from the output that there is a process of running the artisan migrate command after the create project laravel 11 process.

Thus the simple CRUD App development experiment in Laravel 11. Thank you for following until the end. See you in the next post.