Implement CRUD operations in Laravel with preview and upload image

A CRUD application provides the basic database operations, such as SELECT, INSERT, UPDATE and DELETE. In this topic, we will develop a Laravel CRUD application, in which you can view the list of users, add new users, update an existing user and delete a user. While adding we can preview and upload the photo and also change the photo. So, we will show Laravel image CRUD also in this application.

Download Source Code from github.

Watch YouTube Video

Create a Laravel project

Use the below command to create the project lara_crud_image.


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

Laravel Migration

Update .env file for database details. We will be using a database named 'lara_demo' in MySQL, so we updated .env as below:


DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=lara_demo
DB_USERNAME=root
DB_PASSWORD=

We will use Laravel "users" table for this topic. We will add a nullable column "photo" to this table. Let us create a migration to alter the table named 'users'. Run the below command for migration from the terminal.


php artisan make:migration add_photo_to_users_table --table=users

This will create the migration file <yyyy_mm_dd_xxxxxx>_add_photo_to_users_table.php in database/migrations folder.

Update the migration file

Update the above migration file as below:


public function up()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->string('photo')->nullable()->after('email');
        });
    }

So, the "users" table will have an additional column "photo" after the "email" column.

Let us now run Laravel migration to create the default Laravel tables as well as altering the "users" table. Run the below command from the project folder to create the tables.


php artisan migrate

After running migration see the structure of the 'users' table.

laravel crud operation

You can see photo column added in the "users" table.

Also add photo field as $fillable in the User model.


<?php
 protected $fillable = [
        'name',
        'email',
        'password',
        'photo',
    ];

The Controller

Let us take a look at the home page which lists all users in an html table. For each user, there is an edit and delete buttons. A new user can be created by clicking the "New User" button.

laravel crud example

It displays the existing users along with photos. You can add new users. You can edit the details, update the photo and delete the user.

We will have a resource controller with the methods for each of the above functions. Create the controller as below:


php artisan make:controller UserController -r

The controller will have the following methods:

  1. index() method to display all the users.
  2. create() and store() methods are for creating a new user and saving it into the database.
  3. edit() and update() methods to update the user details including the photo.
  4. destroy() method is to delete a user.

You can see that it covers all the CRUD operations (C-Create, R-Read, U-Update, D-Delete). Let us see the code for the controller

index() method


public function index()
    {
        $users = User::latest('id')->get();
        return view('index', compact('users'));
    }

Get all the users and load the index view. This will display the users along with their photos.

create() and store() methods


<?php
    public function create()
    {
        $title ="Add User";
        return view('add_user', compact('title'));
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param    \Illuminate\Http\Request  $request
     * @return  \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        $request->validate(
            [
                'name' => 'required',
                'email' => 'required|email|unique:users',
                'photo' => 'mimes:png,jpeg,jpg|max:2048',
            ]
        );

        $filePath = public_path('uploads');
        $insert = new User();
        $insert->name = $request->name;
        $insert->email = $request->email;
        $insert->password = bcrypt('password');


        if ($request->hasfile('photo')) {
            $file = $request->file('photo');
            $file_name = time() . $file->getClientOriginalName();

            $file->move($filePath, $file_name);
            $insert->photo = $file_name;
        }

        $result = $insert->save();
        Session::flash('success', 'User registered successfully');
        return redirect()->route('user.index');
    }

In the store() method we validate the data submitted from the add user form. Since the photo is not mandatory, we need to check if the photo exists. If it exists, we upload it in the "public/uploads" folder and use the photo file name to insert it into the database.

edit() and update() methods

We are using the same form for both Add and Edit user.


<?php
public function edit($id)
    {
        $title ="Update User";
        $edit = User::findOrFail($id);
        return view('add_user', compact('edit', 'title'));
    }

    /**
     * Update the specified resource in storage.
     *
     * @param    \Illuminate\Http\Request  $request
     * @param    int  $id
     * @return  \Illuminate\Http\Response
     */
    public function update(Request $request, $id)
    {
        $request->validate(
            [
                'name' => 'required',
                'email' => 'required|email|unique:users,email,' . $id,
                'photo' => 'mimes:png,jpeg,jpg|max:2048',
            ]
        );
        $update = User::findOrFail($id);
        $update->name = $request->name;
        $update->email = $request->email;

        if ($request->hasfile('photo')) {
            $filePath = public_path('uploads');
            $file = $request->file('photo');
            $file_name = time() . $file->getClientOriginalName();
            $file->move($filePath, $file_name);
            // delete old photo
            if (!is_null($update->photo)){
                $oldImage = public_path('uploads/' . $update->photo);
                if (File::exists($oldImage)) {
                    unlink($oldImage);
                }
            }
            $update->photo = $file_name;     
        }

        $result = $update->save();
        Session::flash('success', 'User updated successfully');
        return redirect()->route('user.index');
    }

edit() method is called when the user clicks on the Edit button. It uses the ID of the user and gets the data from the database to display the details in the form.

In update() method we are doing the same validation as Add user and checking if the photo is selected. We upload the photo and delete the old photo from the folder. Then update the database with the new values along with the photo.

destroy() method


<?php
public function destroy(Request $request)
    {
        $userData = User::findOrFail($request->user_id); 
        $userData->delete();
        // delete photo if exists
        if (!is_null($userData->photo)){
            $photo = public_path('uploads/' . $userData->photo);
            if (File::exists($photo)) {
                unlink($photo);
            }
        }
        Session::flash('success', 'User deleted successfully');
        return redirect()->route('user.index');
    }

destroy() method is to delete the selected user. It deletes the user row from the "users" table. Also, if a photo exists for the user, it deletes the photo. There will be a delete confirmation from the user.

Routes


Route::get('/', [UserController::class, 'index'])->name('user.index');

Route::group(['prefix' => 'user'], function () {
    Route::get('/create',[UserController::class,'create'])->name('user.create');
    Route::post('/add',[UserController::class,'store'])->name('user.store');
    Route::get('/edit/{id}',[UserController::class,'edit'])->name('user.edit');
    Route::post('/update/{id}',[UserController::class,'update'])->name('user.update');
    Route::post('/delete',[UserController::class,'destroy'])->name('user.delete');
});

Create the blade views

We will create two views, one for listing the users and the other for adding/updating the user. Along with them, we will have one modal for confirmation before deleting.

Below are the files in the resources/views folder:

laravel image preview before upload

header, footer and master are the layouts. add_user.blade.php is to add/update the user. index.blade.php is for listing the users. modal_delete.blade.php is the modal for user confirmation before deletion.

resources/views/layouts/header.blade.php


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

<head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>Laravel CRUD with upload image</title>
    <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1">
    <meta name="_token" content="{{ csrf_token() }}">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" />
    <link rel="stylesheet" type="text/css" media="screen" href="{{ asset('css/style.css') }}" />
    <link rel="stylesheet" type="text/css"
        href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
</head>

resources/views/layouts/footer.blade.php


<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.3/js/bootstrap.min.js"></script>

resources/views/layouts/master.blade.php


@include('layouts.header')

<body>

    @yield('main-content')
    @include('layouts.footer')
    @stack('js')
</body>

</html>

resources/views/index.blade.php


@extends('layouts.master')
@section('main-content')
    <h1>List of Users</h1>
    <div class="container">
        <div class="text-end mb-5"><a class="btn btn-info" href="{{ route('user.create') }}">New User</a>
        </div>
        @if (session('success'))
            <div class="alert alert-success">
                {{ session('success') }}
            </div>
        @endif
        @if (session('error'))
            <div class="alert alert-danger">
                {{ session('error') }}
            </div>
        @endif
        <div class="table-responsive">
            <table class="table table-bordered table-striped">
                <thead>
                    <th>Sr. no.</th>
                    <th>Name</th>
                    <th>Email</th>
                    <th>Photo</th>
                    <th>Action</th>
                </thead>
                <tbody>
                    @forelse($users as $index => $row)
                        <tr>
                            <td>{{ $index + 1 }}</td>
                            <td>{{ $row->name }}</td>
                            <td>{{ $row->email }}</td>
                            <td>
                                <div class="showPhoto">
                                    <div id="imagePreview"
                                        style="@if ($row->photo != '') background-image:url('{{ url('/') }}/uploads/{{ $row->photo }}')@else background-image: url('{{ url('/img/avatar.png') }}') @endif">
                                    </div>
                                </div>
                            </td>
                            <td>
                                <a href={{ route('user.edit', ['id' => $row->id]) }} class="btn btn-info"><i
                                        class="fa fa-pencil"></i> Edit</a>
                                <button class="btn btn-danger"
                                    onClick="deleteFunction('{{ $row->id }}')">Delete</button>
                            </td>
                        </tr>
                    @empty
                        <tr>
                            <td colspan="5">No Users Found</td>
                        </tr>
                    @endforelse
                </tbody>
            </table>
        </div>
    </div>
    @include ('modals.modal_delete')
@endsection

@push('js')
    <script>
        function deleteFunction(id) {
            document.getElementById('delete_id').value = id;
            $("#modalDelete").modal('show');
        }
    </script>
@endpush

resources/views/add_user.blade.php


@extends('layouts.master')
@section('main-content')
    <div class="container">
        <div class="col-md-12">
            <div class="form-appl">
                <div class="title-class">
                    <h2>Laravel CRUD Upload Image</h2>
                </div>
                <h3>{{ $title }}</h3>
                <form class="form1" method="post"
                    action="@if (isset($edit->id)) {{ route('user.update', ['id' => $edit->id]) }}@else{{ route('user.store') }} @endif"
                    enctype="multipart/form-data">
                    @csrf
                    <div class="form-group col-md-12 mb-3">
                        <label for="">Your Name</label>
                        <input class="form-control" type="text" name="name" placeholder="Enter Your Name"
                            value="@if (isset($edit->id)) {{ $edit->name }}@else {{ old('name') }} @endif">
                        @error('name')
                            <div class="error">{{ $message }}</div>
                        @enderror
                    </div>

                    <div class="form-group col-md-12 mb-3">
                        <label for="">Your Email</label>
                        <input class="form-control" type="text" name="email" placeholder="Enter Your Email"
                            value="@if (isset($edit->id)) {{ $edit->email }}@else {{ old('email') }} @endif">
                        @error('email')
                            <div class="error">{{ $message }}</div>
                        @enderror
                    </div>
                    <div class="form-group col-md-12 mb-5">
                        <label for="">Photo</label>
                        <div class="avatar-upload">
                            <div class="avatar-edit">
                                <input type='file' id="imageUpload" name="photo" accept=".png, .jpg, .jpeg"
                                    onchange="previewImage(this)" />
                                <label for="imageUpload"></label>
                            </div>

                            <div class="avatar-preview">
                                <div id="imagePreview"
                                    style="@if (isset($edit->id) && $edit->photo != '') background-image:url('{{ url('/') }}/uploads/{{ $edit->photo }}')@else background-image: url('{{ url('/img/avatar.png') }}') @endif">
                                </div>
                            </div>
                        </div>
                        @error('photo')
                            <div class="error">{{ $message }}</div>
                        @enderror
                    </div>

                    <input type="submit" class="btn btn-primary" value="Submit">
                    <a class="btn btn-danger" href="{{ route('user.index') }}">Cancel</a>
                </form>
            </div>
        </div>
    </div>
@endsection
@push('js')
    <script type="text/javascript">
        function previewImage(input) {
            if (input.files && input.files[0]) {
                var reader = new FileReader();
                reader.onload = function(e) {
                    $("#imagePreview").css('background-image', 'url(' + e.target.result + ')');
                    $("#imagePreview").hide();
                    $("#imagePreview").fadeIn(700);
                }
                reader.readAsDataURL(input.files[0]);
            }
        }
    </script>
@endpush

resources/views/modal_delete.blade.php

laravel crud operation


<!-- Modal -->
<div class="modal fade" id="modalDelete" tabindex="-1" role="dialog"
aria-labelledby="modalDeleteTitle" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
  <div class="modal-content">
    <form action="{{route('user.delete')}}" method="post">
      {{csrf_field()}}
      <input type="hidden" name="user_id" id="delete_id">
      <div>
        <center><h1>!</h1></center>
      </div>

    <div class="modal-body">
      <center><h1>Are You Sure?</h1>
      <h6>You want to Delete the user!</h6>
    </center>
    </div>
      <div class="row" style="margin-bottom: 50px; text-align: center;">
        <div class="col-sm-3"></div>
        <div class="col-sm-3">
          <button type="button" class="btn btn-danger btn-modal" data-bs-dismiss="modal">Cancel</button>
        </div>
        <div class="col-sm-3">
          <button type="submit" class="btn btn-success btn-modal" >Delete</button>
        </div>
        <div class="col-sm-3"></div>
      </div>
    </form>
  </div>
</div>
</div>

Add the below stylesheet:

public/css/style.css


* {box-sizing: border-box;
}
body {
  margin: 0;
  font-family: Helvetica, sans-serif;;
}
h1,h4, h3 {
  text-align: center;
  margin-bottom: 20px;
  margin-top: 10px;
}

.container{
    min-height: 600px;;
}
footer{
    text-align: center;
}
.form1{
    width:50%;
    margin: auto;
}

.error{
  color:red;
}

.modal-content{
color:#fff;
background: #32243c;
}
table{
  margin: auto;
}
table>thead{
  background-color:#2b5171;
  color:#fff;
}

.form-appl{
  padding:5px; 
  margin-top:50px;
}
.title-class{
font-size:30px; 
text-align:center;
}
.err-msg{
  color: red;
}

.avatar-upload {
  position: relative;
  max-width: 205px;
}
.avatar-upload .avatar-edit {
  position: absolute;
  right: 64px;
  z-index: 1;
  top: -5px;
}
.avatar-upload .avatar-edit input {
  display: none;
}
.avatar-upload .avatar-edit input + label {
  display: inline-block;
  width: 34px;
  height: 34px;
  margin-bottom: 0;
  border-radius: 100%;
  background: #f3ecec;
  border: 1px solid transparent;
  box-shadow: 0px 2px 4px 0px rgba(0, 0, 0, 0.12);
  cursor: pointer;
}
.avatar-upload .avatar-edit input + label:hover {
  background: #c3dbf4;
  border-color: #918484;
}
.avatar-upload .avatar-edit input + label:after {
  content: "\f040";
  font-family: 'FontAwesome';
  color: #757575;
  position: absolute;
  top: 5px;
  left: 3px;
  right: 0;
  text-align: center;
  margin: auto;
}
.avatar-upload .avatar-preview {
  width: 67%;
  height: 147px;
  position: relative;
  border-radius: 3%;
  border: 6px solid #F8F8F8;
}
.showPhoto{
  width: 51%;
  height: 54px;
  margin:auto;
}
.showPhoto >div{
  width: 100%;
  height: 100%;
  border-radius: 50%;
  background-size: cover;
  background-repeat: no-repeat;
  background-position: center;
}
.avatar-upload .avatar-preview > div {
  width: 100%;
  height: 100%;
  border-radius: 3%;
  background-size: cover;
  background-repeat: no-repeat;
  background-position: center;
}
.btn-modal{
  padding:10px; 
  margin-left: -10px; 
  width:120px;
  }

Test the application

Run php artisan serve command from the project root to start the development server:

From the browser run localhost:8000. Test the below cases:

  • List users with photo
  • Add New users with/without photo
  • Update user details with/without photo
  • Delete a user, check if the user's photo is also deleted from the server.

Download Source Code from github.

Watch YouTube Video