Today we are going to explore on CRUD operations in Laravel. In the previous articles in our Laravel for beginners series, we built a small sample library management system, where we can store books and list them, however our library is still very limited, we cannot edit the books we added, nor delete any book that we need to remove from the database.
Earlier we have also discussed about
This is what we will implement in this article, and in our journey to extend the functionality of our app, we will learn about routing, models and views.
What is CRUD Operations ?
CRUD is a term used in software development that refers to the four operations that models should be able to do, which are Create, Read, Update, and Delete.
If your application uses a database, which is the case in the vast majority of applications, then you will frequently need to perform CRUD operations for your tables, which are represented with models in Laravel.
Each letter in the CRUD acronym has a corresponding HTTP request method.
CRUD OPERATIONS | HTTP REQUEST METHOD |
Create | POST |
Read | Get |
Update | PUT or PATCH |
Delete | Delete |
For each one of these four operations, we need to create a specific route in our web.php file, add to them another three routes, one to list all available records usually called index, and the other two are to show the pages used to create and update the model.
This is how a typical full route list for a model, it has the above seven routes for all the required model operations, and displaying pages.
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\BookController;
Route::controller(BookController::class)->group(function(){
Route::get('books', 'index')->name('books.index');
Route::post('books', 'store')->name('books.store');
Route::get('books/create', 'create')->name('books.create');
Route::get('books/{book}', 'show')->name('books.show');
Route::put('books/{book}', 'update')->name('books.update');
Route::delete('books/{book}', 'destroy')->name('books.destroy');
Route::get('books/{book}/edit', 'edit')->name('books.edit');
});
As you can see we grouped all the routes related to the same model, and pointing to the same controller together using the Route::controller() method, also all the routes are named so we can reference them later in our code and views.
The naming scheme we use here is the preferred standardized names used in Laravel, and the best practice is to match the route name with the method name in the controller.
Here are the standard routes and methods recommended by Laravel, we use books here as an example of course. But the name of the model should change for sure based on the model you want to work with.
HTTP Method | URI | Method | Route Name |
GET | /books | index | books.index |
GET | /books/create | create | books.create |
POST | /books | store | books.store |
GET | /books/{book} | show | books.show |
GET | /books/{book}/edit | edit | books.edit |
PUT/PATCH | /books/{book} | update | books.update |
DELETE | /books/{book} | destroy | books.destroy |
Resource Controller and Route
For the above seven routes, we need to create the relevant seven methods in the BookController. However as Laravel is the nice framework it is, it provides us with a simple method to automatically create a controller with these default seven methods using the following Artisan CLI.
php artisan make:controller BookController --resource
//if you create the controller with the model in the same command add r
php artisan make:model Book -crm
This command will create a resource controller with all the necessary methods. Here is how the BookController will look like if we create it as a resource controller.
<?php
namespace App\Http\Controllers;
use App\Models\Book;
use Illuminate\Http\Request;
class BookController extends Controller
{
public function index()
{
//
}
public function create()
{
//
}
public function store(Request $request)
{
//
}
public function show(Book $book)
{
//
}
public function edit(Book $book)
{
//
}
public function update(Request $request, Book $book)
{
//
}
public function destroy(Book $book)
{
//
}
}
You can go another step further to simplify things. Remember the route statement with the seven routes, Laravel can replace them with a single line statement that does the same functionality.
Route::resource('books', BookController::class);
If you add this line to the routing file (web.php), it will have the same functionality as the original seven routes, with names as well, following the standard naming scheme.
So if you know you will use all or most of the methods in the resource controller, and their routes, you better use the Route::resource() method, combined with a resource controller.
Adding More Views
Now, as we explained the principals needed to add the CRUD operations in route and controllers, let’s actually do that. And we will start with the show functionality. This is where the details of each book will be displayed.
We need to create a view that will list the book details, so head to the /resources/views folder and create a file named show.blade.php. This file will show the book details, here is the blade file contents.
<html lang="en">
<head>
<meta charset="utf-8">
<title>Book List</title>
<style>
.item {
margin-bottom: 20px;
}
.label {
display: inline-block;
font-weight: bold;
width: 100px;
}
</style>
</head>
<body>
<div style="padding-bottom: 20px">
<h1>Book details</h1>
<a href="/">Back</a>
</div>
<div>
<div class="item">
<span class="label">Book Name:</span>
<span>{{$book->name}}</span>
</div>
<div class="item">
<span class="label">Author:</span>
<span>{{$book->author}}</span>
</div>
<div class="item">
<span class="label">ISBN:</span>
<span>{{$book->isbn}}</span>
</div>
<div class="item">
<span class="label">Price:</span>
<span>$ {{$book->price}} </span>
</div>
<div class="item">
<span class="label">Published at:</span>
<span>{{$book->published}}</span>
</div>
<div class="item">
<span class="label">Quantity:</span>
<span>{{$book->quantity}}</span>
</div>
<div class="item">
<span class="label">description:</span>
<span>{{$book->description}}</span>
</div>
</div>
</body>
</html>
We only added the basic HTML code, no fancy styling, as our aim here is to mainly show the functionality itself. The above code has 7 divs for the 7 book properties we have. And Blade variables in curly brackets will be replaced upon display with actual values.
If you inspect the view code, you will find that we have a $book variable, where did that come from? Of course it should come from the controller, we still need to add the code to the controller, in the show() method of the controller, as shown below.
public function show($id)
{
$book = Book::findOrFail($id);
return view('show', compact('book'));
}
The show() method receives the id of the required book with the route. Then in the first statement we try to retrieve the requested book using the findOrFail() method, if a book with this id is found in the database it is passed to the $book variable.
Then in the second statement we call the show view and pass this variable to it, this is the same variable we used in the Blade template above, note that they must have the same name.
What if there is no book in the database with this id? The findOrFail() method will redirect the user to the 404 not found page.
Now to test this page, open your browser, and put the url http://localhost:8000/books/1 there, here is what you should see.
Modifying the Index View
Now we need to make a few changes to our index page that displays the book list, to accommodate the new functionalities in our app.
For example we need to add a link to the book details page for each book. We also need to add links to the edit and delete functionalities.
The below is the table part of the view, with the modifications.
<table style="width: 100%;" border="1" cellspacing="1">
<tbody>
<tr>
<th>ID</th>
<th>Name</th>
<th>Author</th>
<th>ISBN</th>
<th>actions</th>
</tr>
@foreach($books as $book)
<tr>
<td>{{$book->id}}</td>
<td>
<a href="{{ route('books.show',['book' => $book->id]) }}">
{{$book->name}}
</a>
</td>
<td>{{$book->author}}</td>
<td>{{$book->isbn}}</td>
<td>
<a href="{{ route('books.edit', ['book' => $book->id]) }}">
Edit
</a>
<form action="{{ route('books.destroy', ['book' => $book->id]) }}"
method="Post" style="display: inline-block">
@method('delete')
@csrf
<button type="submit">Delete</button>
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
We added a link in the name column for each book that points to the details page, this link has the format /books/book_id, however, we used the named route feature here, we used the route() method, add the route name and the parameter it needs which is the book id.
This link will open the book details page we just created in the above section.
Then we added a new column for the actions edit and delete. Again we used the named routes for these actions, books.edit and books.destroy, and added the required parameter book id.
Note that in the edit functionality we used a link to redirect us to the edit page that we will create later. But for the delete function we used a form with a submit button, as we don’t need another page we will delete the record here directly.
In the form you can see the @method(‘delete’) statement, which we use to indicate that this form’s action is actually a Delete action rather than a typical Post.
This is required, and if you don’t use it the route will not be identified correctly. And we also added the @csrf directive for the CSRF protection as explained in the previous article.
Why do we prefer named routes?
Although you can put the actual routes in your Blade files and controller functions, using the named routes has the benefit of flexibility.
Imagine you have a big application, and certain routes are used in multiple locations in your application views and controllers.
Then one day you decide you want to update the application and this requires some changes to the routes, you will need to go through all of the locations you use that route and change them manually one by one, and there will be a very big chance you forget some of them.
Using named routes is like giving your routes nicknames. You define the names in the route files, then you use these names in your application instead of the actual routes.
Then at any time if you want to change the route, you will only need to change the routes in the route file. And this change will be applied automatically anywhere the name of this route is used.
Implementing the Delete function
We already added the delete button to the index view, and the route is already defined in the Route::resource() method, we only need the actual function to the controller inside the destroy() function. Here is the code.
public function destroy($id)
{
$book = Book::findOrFail($id);
$book->delete();
return redirect('/');
}
As you can see the code is straight forward, we pass the id of the book in the request, which is received in the destroy function.
We find the book and apply the delete() method. That’s it. As before if the book id is not found the user will be directed to the 404 not found page.
Now try the delete button beside any of the books you created. You will find that the index page is reloaded, and that book is deleted.
Great!!.. But Wait.
Something is missing. What if the user clicks the delete button by mistake? The book will be deleted without any chance for a second thought. So how can we solve this?
By adding a small java script code in the view file. This code will display an alert. That will ask the user if he is sure he wants to delete this book. If he clicks Ok the request will be submitted, otherwise nothing will happen.
<form action="{{ route('books.destroy', ['book' => $book->id]) }}"
method="Post" style="display: inline-block"
onsubmit="return confirm('Are you sure you want to delete this book?');">
@method('delete')
@csrf
<button type="submit">Delete</button>
</form>
Editing and Updating Records
Now to the final part of our CRUD operations, updating the records. This will require us to build another view to edit the record data.
This view will be very similar to the create view with 2 differences. The first is that it should open preloaded with the data of the record we need to edit.
And the second is that instead of saving a new record, we will update an existing one. Here is the code of the update view.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Book List</title>
</head>
<body>
<div style="padding-bottom: 20px">
<h1>Enter book details</h1>
<a href="/">Back</a>
</div>
<div>
<form action="{{route('books.update', ['book' => $book->id])}}" method="post">
@method('put')
@csrf
<div style="margin: 10px">
<label style="display: block" for="name">Name</label>
<input type="text" name="name" id="name" value="{{ $book->name }}">
@error('name')
<div style="color: red">{{ $message }}</div>
@enderror
</div>
<div style="margin: 10px">
<label style="display: block" for="author">Author</label>
<input type="text" name="author" id="author" value="{{ $book->author }}">
@error('author')
<div style="color: red">{{ $message }}</div>
@enderror
</div>
<div style="margin: 10px">
<label style="display: block" for="isbn">ISBN</label>
<input type="text" name="isbn" id="isbn" value="{{ $book->isbn }}">
@error('isbn')
<div style="color: red">{{ $message }}</div>
@enderror
</div>
<div style="margin: 10px">
<label style="display: block" for="price">Price</label>
<input type="number" name="price" id="price" value="{{ $book->price }}">
@error('price')
<div style="color: red">{{ $message }}</div>
@enderror
</div>
<div style="margin: 10px">
<label style="display: block" for="published">Published at</label>
<input type="date" name="published" id="published" value="{{ $book->published }}">
@error('published')
<div style="color: red">{{ $message }}</div>
@enderror
</div>
<div style="margin: 10px">
<label style="display: block" for="quantity">Quantity</label>
<input type="number" name="quantity" id="quantity" value="{{ $book->quantity }}">
@error('quantity')
<div style="color: red">{{ $message }}</div>
@enderror
</div>
<div style="margin: 10px">
<label style="display: block" for="description">Description</label>
<textarea name="description" id="description" style="width: 300px"
value="{{ $book->description }}"></textarea>
</div>
<button type="submit" style="margin: 10px">Update</button>
</form>
</div>
</body>
</html>
As you can see it is very similar to the create view. The main differences are the inputs have the values of the book properties, loaded with the view. Also the action of the form submit is different. Here it points to the update route, and as before we used the named route.
Now to the controller part. We will need to add code to the 2 functions edit() and update(). The first function will handle the routing part to the update view, and the second one will handle saving the updated record to the database.
public function edit($id)
{
$book = Book::findOrFail($id);
return view('update', compact('book'));
}
public function update(Request $request, $id)
{
$request->validate([
'name' => ['required', 'max:255'],
'author' => ['required', 'min:5'],
'isbn' => ['required', 'unique:books,isbn,' . $id, 'digits:13'],
'price' => ['required', 'numeric'],
'quantity' => ['nullable', 'numeric'],
'published' => ['nullable', 'date'],
'description' => ['nullable'],
]);
$book = Book::findOrFail($id);
$book->update($request->all());
return view('show', compact('book'));
}
The code in the edit() function is easy. You just retrieve the book to update and pass it to the update view, very similar to the show() function.
The update() function receives the update request. Then we validate the request data, same as we did in the create function.
The only difference is the validation of the ISBN field, because this field is unique. We need to let the validation method know the id of the field we are updating. So it doesn’t count the new input as a duplication and block it.
Now open the browser, go to the application index page, and choose a book, click on the edit link. The update page will open, similar to the one in the below screenshot.
Now edit the book details, & click the update button. Your book will be saved and you will be redirected to the details page of this book.
Conclusion
At this point our small library application is complete. We can now create books, edit or delete them, and of course list them. And in this journey we learned the basics about views, routing, models and controllers.
In the coming articles of this series we will discuss more advanced Laravel concepts, so do follow us.