- Published on
Build Simple Laravel 8 CRUD Application
Which is better, the Laravel framework or another framework? Yes, this is one of the questions we often find, and we sometimes ask ourselves. And to find the answer to this question, I decided to learn Laravel 8 and build a simple CRUD application. Of course, to learn seriously, there is a learning roadmap that you can follow to learn the Laravel 8 framework. This article was created to document learning when Laravel 8 leaks into gross material.
Table of Contents
CRUD App Overview
Compared to the framework we built in the previous post, the simple crud application we will create in this tutorial is a simple blog. As you build this application, you will learn a few things related to interacting with the database, such as creating data, reading data, updating data, and deleting data. To handle interacting with the database, we will use Eloquent, the Laravel framework's object-relational mapper (ORM).
Step 1: Setup Laravel 8 Project
First we create new laravel 8 project using composer
. Open terminal, and run below command to create laravel 8 project.
composer create-project --prefer-dist laravel/laravel blog
After we run the command above, we can see the laravel 8 installation process. After the installation process is complete we can see a new project directory called blog
. We go to the project directory by running the command.
cd blog
Then we can try first whether the installation process of our Laravel 8 project is successful. We tested it by running the local development server using the Artisan CLI command serve
.
php artisan serve
We can open http://127.0.0.1:8000
in our browser for a test installation. When we open the url in the browser, we can see the start page of laravel 8, a sign that the laravel 8 installation was successful.
Step 2: Setup Database
The next step is to set the database configuration of our project. The story is in our project this time, the name of the database that we will use is db_blog
, then the mysql credential on our laptop, we have the username admin
and the password is password
.
Now, we create a database with the name db_blog
, either via phpmyadmin
or through the terminal.
After creating the database, to connect to the db_blog
database, we try to open the .env
file in a text editor, then adjust the database configuration, as below.
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=db_blog
DB_USERNAME=admin
DB_PASSWORD=password
When finished, save the .env
file again before continuing to the next step.
Step 3: Create Model dan Migration Files
In this step we will try to create the model and migration with one artisan
command. Back to the terminal or cmd, we run the artisan
command below.
php artisan make:model Post -m
We can see that there are two files that have been successfully generated using the command above, the first one is the model file app/Models/Post.php
and the second one is the migration file database/migrations/2021_08_18_043743_create_posts_table.php
. Now for the record, the name of the migration file is adjusted to the date when the migration file was created.
For example, friends ask, if without the migration file, what is the artisan
command that we use? We can directly type the command php artisan make:model Post
without any sign or -m
option. To generate the migration file when we generate the model, we can add the --migration
or -m
option as shown above.
Next, we try to set or define what model attributes we will add during the mass assignment or insert new data into the database. Open the app/Models/Post.php
file, then add the $fillable
properties in the Post class.
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
use HasFactory;
protected $fillable = [
'title', 'content', 'slug', 'status'
];
}
We can see in the above line of code, in the $fillable
properties, we allow model attributes, namely title
, content
, slug
and status
to be inserted into the database when we use the create method.
later. Another option we can use the $guarded
attribute.
After setting the mass assignable in the model, then we will create a table using the migration file that we generated earlier. Open the migration file 2021_08_18_043743_create_posts_table.php
(don't forget the name matches the date the migration file was generated, so it's definitely a different name on the date). Next we define what columns are in the posts
table that we will create.
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreatePostsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('content');
$table->string('slug');
$table->smallInteger('status');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('posts');
}
}
Once done, we run the migration using the following artisan
command.
php artisan migrate
We can see that there are several new tables in our project database, including the posts
table we just defined in the migration file.
Step 4: Create View Post Data
Alright, our project preparation is complete, then we can start to create the first feature, which is a feature to display data. Of course, because our case study is about a blog, the data we will display is post data from our blog.
To handle the data to be displayed, we try to create a new controller, namely PostController
using the following artisan
command.
php artisan make:controller PostController
After running the above command successfully, you will see a new controller file appear in the app/Http/Controllers
directory called PostController.php
file. Open the file app/Http/Controllers/PostController.php
in a text editor. Open the app/Http/Controllers/PostController.php file and add the index() method to the PostController class.
<?php
namespace App\Http\Controllers;
use App\Models\Post; // load Post model
use Illuminate\Http\Request;
class PostController extends Controller
{
public function index()
{
$posts = Post::latest()->get();
return view('posts.index', compact('posts'));
}
}
As you can see in the above line of code, in the index()
method we take the post data, then we pass the post data to the index.blade.php
view via the view()
method as the second parameter. . Because we are using a model, don't forget to import the class first using the use
statement.
<?php
namespace App\Http\Controllers;
use App\Models\Post; // add use statement
use Illuminate\Http\Request;
class PostController extends Controller
{
// PostController class content
}
OK, let's continue.
In the view()
method, there is a posts.index
parameter which means we will create a new folder, called posts
in resources/views
. After that we create a new file resources/views/posts/index.blade.php
. Then we type the following 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>Post List - Tutorial CRUD Laravel 8 @ gungunpriatna.com</title>
<link
rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"
/>
</head>
<body>
<div class="container mt-5">
<div class="row">
<div class="col-md-12">
<!-- Notif using flash session data -->
@if (session('success'))
<div class="alert alert-success">{{ session('success') }}</div>
@endif @if (session('error'))
<div class="alert alert-error">{{ session('error') }}</div>
@endif
<div class="card border-0 shadow rounded">
<div class="card-body">
<a href="{{ route('post.create') }}" class="btn btn-md btn-success mb-3 float-right"
>New Post</a
>
<table class="table table-bordered mt-1">
<thead>
<tr>
<th scope="col">Title</th>
<th scope="col">Status</th>
<th scope="col">Create At</th>
<th scope="col">Action</th>
</tr>
</thead>
<tbody>
@forelse ($posts as $post)
<tr>
<td>{{ $post->title }}</td>
<td>{{ $post->status == 0 ? 'Draft':'Publish' }}</td>
<td>{{ $post->created_at->format('d-m-Y') }}</td>
<td class="text-center">
<form
onsubmit="return confirm('Are you sure ?');"
action="{{ route('post.destroy', $post->id) }}"
method="POST"
>
<a href="{{ route('post.edit', $post->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 not found</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
</body>
</html>
Yes, in our view there are several blade directives, namely @forelse
which is used to display data, and @if
to display notifications. In addition, there is also a 'route()' method to go to the create
, edit
and delete
data pages.
Next we try to register a new route, we open routes/web.php
file. We add a new route to open our post data page in the file.
<?php
use App\Http\Controllers\PostController; //load controller post
// another line of code
Route::resource('post', PostController::class);
One route above declares several routes which can handle multiple actions for resource
such as create
, show
, update
, delete
and others. You can check existing routes using the artisan
command.
php artisan route:list
The command displays the route contained in our laravel application.
+--------+-----------+---------------------+--------------+------------------------------------------------------------+------------------------------------------+
| Domain | Method | URI | Name | Action | Middleware |
+--------+-----------+---------------------+--------------+------------------------------------------------------------+------------------------------------------+
| | GET|HEAD | post | post.index | App\Http\Controllers\PostController@index | web |
| | POST | post | post.store | App\Http\Controllers\PostController@store | web |
| | GET|HEAD | post/create | post.create | App\Http\Controllers\PostController@create | web |
| | GET|HEAD | post/{post} | post.show | App\Http\Controllers\PostController@show | web |
| | PUT|PATCH | post/{post} | post.update | App\Http\Controllers\PostController@update | web |
| | DELETE | post/{post} | post.destroy | App\Http\Controllers\PostController@destroy | web |
| | GET|HEAD | post/{post}/edit | post.edit | App\Http\Controllers\PostController@edit | web |
+--------+-----------+---------------------+--------------+------------------------------------------------------------+------------------------------------------+
From the list of routes above, we can also see what methods should be in the PostController
class.
Step 5: Create a New Post
We have created a page with a list of posts, but we can't add new data yet. Now, let's add the ability to create a new post. To add this functionality, open the "PostController" controller again and then create a new post by adding a new method to render the form page.
<?php
// ... previous line of code
class PostController extends Controller
{
// ... previous line of code
public function create()
{
return view('posts.create');
}
}
Then create a new view according to the view(
posts.create)method in the above line of code. Create a new view file
resources/views/posts/create.blade.php` and try the following line 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 Post - Tutorial CRUD Laravel 8 @ gungunpriatna.com</title>
<link
rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"
/>
<!-- include summernote css -->
<link
href="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote.min.css"
rel="stylesheet"
/>
</head>
<body>
<div class="container mt-5 mb-5">
<div class="row">
<div class="col-md-12">
<!-- notif using flash session data -->
@if (session('success'))
<div class="alert alert-success">{{ session('success') }}</div>
@endif @if (session('error'))
<div class="alert alert-error">{{ session('error') }}</div>
@endif
<div class="card border-0 shadow rounded">
<div class="card-body">
<form action="{{ route('post.store') }}" method="POST">
@csrf
<div class="form-group">
<label for="title">Title</label>
<input
type="text"
class="form-control @error('title') is-invalid @enderror"
name="title"
value="{{ old('title') }}"
required
/>
<!-- error message for title -->
@error('title')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="form-group">
<label for="status">Publish Status</label>
<select name="status" class="form-control" required>
<option value="1" selected>Publish</option>
<option value="0">Draft</option>
</select>
</div>
<div class="form-group">
<label for="content">Content</label>
<textarea
name="content"
id="content"
class="form-control @error('content') is-invalid @enderror"
rows="5"
required
>
{{ old('content') }}</textarea
>
<!-- error message for content -->
@error('content')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<button type="submit" class="btn btn-md btn-primary">Save</button>
<a href="{{ route('post.index') }}" class="btn btn-md btn-secondary">back</a>
</form>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script
src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"
integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN"
crossorigin="anonymous"
></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
<!-- include summernote js -->
<script src="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote.min.js"></script>
<script>
$(document).ready(function () {
$('#content').summernote({
height: 250, //set editable area's height
})
})
</script>
</body>
</html>
In the above form, the form action points to the post.store
path, and when checked with the artisan command, this path is connected to the store()
method in the PostController
class. Then add a store()
method to the PostController
which will handle the process of saving the data.
<?php
// ... previous line of code
use Illuminate\Support\Str; // tambahkan kode ini
class PostController extends Controller
{
// ... previous line of code
public function store(Request $request)
{
$this->validate($request, [
'title' => 'required|string|max:155',
'content' => 'required',
'status' => 'required'
]);
$post = Post::create([
'title' => $request->title,
'content' => $request->content,
'status' => $request->status,
'slug' => Str::slug($request->title)
]);
if ($post) {
return redirect()
->route('post.index')
->with([
'success' => 'New post has been created successfully'
]);
} else {
return redirect()
->back()
->withInput()
->with([
'error' => 'Some problem occurred, please try again'
]);
}
}
}
The algorithm for adding data to the store() method consists of three parts: the validation process, the data storage process, and the redirection to the page after the store process completes.
Validation process uses validate()
method with $request
as first parameter and validation rules as second parameter. For example, this tutorial uses only 'required', 'string' for string datatype, and 'max:value' for field field. If you do not fill out the form according to the validation rules, the web returns to the Add Data form page and displays an error message in each field. We can see the create.blade.php file which contains the following code.
<!-- error message for title -->
@error('title')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
Use the @error directive to check for validation error messages for a particular attribute. The @ error
directive allows you to display error messages using the echo
variable $message
.
If the validation process is appropriate, there is a data insertion process. The code section of the data insertion process uses the create()
method in this tutorial. This method takes an array of attributes and performs an operation that inserts data into the database.
$post = Post::create([
'title' => $request->title,
'content' => $request->content,
'status' => $request->status,
'slug' => Str::slug($request->title)
]);
Oh yes, for the slug
attribute, we use the str
helper to generate a URL friendly slug. Therefore there is an additional use
statement before the PostController
class declaration.
use Illuminate\Support\Str;
And the last part is "redirected" to the page determined according to the conditions for successful data insertion. If the insert operation is successful, we will be taken to the post list page, and if an error occurs, we will be returned to the form page.
Step 6: Update post data
Once we have added the data, you'll see that each row of data in the post list table has an Edit button. We can check again. We can find the following line of code in the index.blade.php
file.
<a href="{{ route('post.edit', $post->id) }}" class="btn btn-sm btn-info shadow">Edit</a>
The edit button leads to the post.edit
route with $post->id
as the parameter, where this route points to the edit()
method in the PostController
class. Now we reopen PostController
, then we add the edit()
method to display the edit data page.
<?php
// ... previous line of code
class PostController extends Controller
{
// ... previous line of code
public function edit($id)
{
$post = Post::findOrFail($id);
return view('posts.edit', compact('post'));
}
}
In the edit()
method above, we try to pass the $post
data which contains post data based on the $id
parameter to the edit.blade.php
view by writing in the second parameter the view()
method.
Next we create a new view file calledresources/views/posts/edit.blade.php
, then we type the following line 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 Post - Tutorial CRUD Laravel 8 @ gungunpriatna.com</title>
<link
rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css"
/>
<!-- include summernote css -->
<link
href="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote.min.css"
rel="stylesheet"
/>
</head>
<body>
<div class="container mt-5 mb-5">
<div class="row">
<div class="col-md-12">
<!-- notif using flash session data -->
@if (session('success'))
<div class="alert alert-success">{{ session('success') }}</div>
@endif @if (session('error'))
<div class="alert alert-error">{{ session('error') }}</div>
@endif
<div class="card border-0 shadow rounded">
<div class="card-body">
<form action="{{ route('post.update', $post->id) }}" method="POST">
@csrf @method('PUT')
<div class="form-group">
<label for="title">Title</label>
<input
type="text"
class="form-control @error('title') is-invalid @enderror"
name="title"
value="{{ old('title', $post->title) }}"
required
/>
<!-- error message for title -->
@error('title')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="form-group">
<label for="status">Publish Status</label>
<select name="status" class="form-control" required>
<option value="1" {{ $post->status == 1 ? 'selected':'' }}>Publish</option>
<option value="0" {{ $post->status == 0 ? 'selected':'' }}>Draft</option>
</select>
</div>
<div class="form-group">
<label for="content">Content</label>
<textarea
name="content"
id="content"
class="form-control @error('content') is-invalid @enderror"
name="content"
id="content"
rows="5"
required
>
{{ old('content', $post->content) }}</textarea
>
<!-- error message for content -->
@error('content')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<button type="submit" class="btn btn-md btn-primary">Update</button>
<a href="{{ route('post.index') }}" class="btn btn-md btn-secondary">back</a>
</form>
</div>
</div>
</div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script
src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js"
integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN"
crossorigin="anonymous"
></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
<!-- include summernote js -->
<script src="https://cdn.jsdelivr.net/npm/summernote@0.8.18/dist/summernote.min.js"></script>
<script>
$(document).ready(function () {
$('#content').summernote({
height: 250, //set editable area's height
})
})
</script>
</body>
</html>
The view you create for the post data edit page is not much different from the post data add page, except for the following sections:
<form action="{{ route('post.update', $post->id) }}" method="POST">@csrf @method('PUT')</form>
You can see it in the Forms section. Since HTML forms cannot make PUT requests, we add a PUT method first for the update process. Second, the form action points to the post.update
path with an ID as a parameter.
Now create a new method to handle data update process in PostController
according to form action.
<?php
// ... previous line of code
class PostController extends Controller
{
// ... previous line of code
public function update(Request $request, $id)
{
$this->validate($request, [
'title' => 'required|string|max:155',
'content' => 'required',
'status' => 'required'
]);
$post = Post::findOrFail($id);
$post->update([
'title' => $request->title,
'content' => $request->content,
'status' => $request->status,
'slug' => Str::slug($request->title)
]);
if ($post) {
return redirect()
->route('post.index')
->with([
'success' => 'Post has been updated successfully'
]);
} else {
return redirect()
->back()
->withInput()
->with([
'error' => 'Some problem has occured, please try again'
]);
}
}
}
The algorithm of the update()
method is not very different from the store()
method, which inserts data, except for the data update process. In the data update process, you use the findOrFail()
method to get the post data based on the $id
parameter, and then use the update()
method to update the data with an array containing the properties as parameters.
$post = Post::findOrFail($id);
$post->update([
'title' => $request->title,
'content' => $request->content,
'status' => $request->status,
'slug' => Str::slug($request->title)
]);
Step 7: Delete Post Data
The last feature of the CRUD to add to the project is the post-data deletion feature. Open the "PostController.php" file and add the "destroy()" method to the "PostController" class. This method handles the process of deleting data that has an ID as a parameter.
<?php
// ... previous line of code
class PostController extends Controller
{
// ... previous line of code
public function destroy($id)
{
$post = Post::findOrFail($id);
$post->delete();
if ($post) {
return redirect()
->route('post.index')
->with([
'success' => 'Post has been deleted successfully'
]);
} else {
return redirect()
->route('post.index')
->with([
'error' => 'Some problem has occurred, please try again'
]);
}
}
}
The algorithm for deleting this data can be divided into three parts: confirming the data by ID, deleting the data, and redirecting to the post list page. In the data validation section, this $id
is based on the post ID we wrote to the post list page or theindex.blade.php
view. The index.blade.php
view has the following line of code:
<form
onsubmit="return confirm('Are you sure ?');"
action="{{ route('post.destroy', $post->id) }}"
method="POST"
>
<a href="{{ route('post.edit', $post->id) }}" class="btn btn-sm btn-info shadow">Edit</a>
@csrf @method('DELETE')
<button type="submit" class="btn btn-sm btn-danger shadow">Delete</button>
</form>
The data is retrieved by ID using the findOrFail()
method. If the data is found, it will be stored in the $post
variable, otherwise the page will display an error. Once we find the data, use the delete()
method to delete it.
$post->delete();
After that we switch back to the post list page, then display a message according to the conditions of success or failure of the delete process.
Finally, all steps in this Laravel 8 CRUD tutorial are complete. Now we can immediately relaunch the app with the artisan command.
php artisan serve
Then open url http://127.0.0.1:8000/post
in browser. After that, we can try to add data, edit data and also delete data.
Conclusion
Which one is better the Laravel framework or another framework? That's a question that we often find in programming forums and may be a question that we often to ask. After studying laravel 8 to make this simple crud app, there are a few things I found. Yes, more or less I've found the answer. How about you? Have you found the answer?