At present, every web application requires CRUD (create, read, update, delete) functionality. Laravel provides awesome features such as routing, resource controller to create CRUD operations. In this tutorial, you’re going to learn Laravel 8 CRUD step by step. Moreover, we’ll take advantage of laravel resource controller and resource routing. With this in mind, I’m creating posts with Laravel 8 CRUD operations.
Post Contents
# Prerequisite
First of all, install laravel with the following commands. Laravel 8 comes with UI packages for authentication. As this tutorial for only CRUD operation, we’ll stay out of it.
composer create-project --prefer-dist laravel/laravel posts
cd posts
php artisan serve
As a result, the development server will be started on localhost:8000. In the long run, we need flash messages to display. It is useful for creating, updating, or deleting posts. With this intention, I’m using laracasts/flash composer package. This package gives bootstrap optimize flash messages with easy setup. So, let’s install the package first. I’ll set up this package when will I create a layout file for this CRUD application.
composer require laracasts/flash
Finally, update .env file with your database credentials.
/* .env */
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=posts
DB_USERNAME=root
DB_PASSWORD=
Creating posts CRUD, we need controllers, models, views, migrations, form request validations, and routes. I’m using form request validations instead of creating directly into controllers. It’s good practice to do validations outside of the controller’s business logic.
# Creating Controller, Model And Migration
Instead of creating controllers (resource- in this tutorial), models, migration with separate commands, you can do it with one command. To do that, create a model with -mcr option, where m is for migration, c is for a controller, and option r will make controller resource.
php artisan make:model Post -mcr
# Migrating Database
After creating files, let’s create a posts table first. As you know, the above command will create a migration file for the posts table. So, let’s create a schema for the posts table and migrate it.
/* database > migrations > *_create_posts_table.php */
<?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', 100);
$table->text('description');
$table->boolean('is_active')->default(true);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('posts');
}
}
php artisan migrate
# Resource Routing
Equally important, this CRUD application requires URL routing. In other words, this application needs routes like add, store, edit, update, delete, and show. Rather than doing it in separate routes, we’ll use a resource route for this application. As a result, it’ll create all of the above routes automatically.
/* routes > web.php */
<?php
use Illuminate\Support\Facades\Route;
Route::resource('posts', 'App\Http\Controllers\PostController');
# Layouts & Views
Again, we need layouts and views for this CRUD application. Let’s create a basic layout master.blade.php with bootstrap included. Formerly, we installed laracasts/flash package. So let’s include a flash message in this layout file. So, it will extend all of our views. Also, let’s create posts pages too.
/* resources > views > layouts > master.blade.php */
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>@yield('title', env('APP_NAME'))</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
</head>
<body>
<div class="container my-5">
@include('flash::message')
@yield('content')
</div>
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
<script>
$('div.alert').not('.alert-important').delay(3000).fadeOut(350);
</script>
</body>
</html>
// Create following files into posts folder
resources > views > posts > index.blade.php
resources > views > posts > create.blade.php
resources > views > posts > edit.blade.php
resources > views > posts > show.blade.php
Finally, the setup is done for Laravel 8 CRUD. Now, let’s create a basic application.
# Create Post
To create a post, open posts > create.blade.php and put the following code. This file contains basic bootstrap form markup that extends master.blade.php. Moreover, this file had validation logic too.
/* resources > views > posts > create.blade.php */
@extends('layouts.master')
@section('title') Create Post @endsection
@section('content')
<form action="{{ route('posts.store') }}" method="post">
@csrf
<div class="card card-default">
<div class="card-header">
Create Post
</div>
<div class="card-body">
<div class="form-group">
<label for="title">Title</label>
<input type="text" class="form-control @if($errors->has('title')) is-invalid @endif" name="title" value="{{ old('title') }}">
@if($errors->has('title'))
<div class="invalid-feedback">{{ $errors->first('title') }}</div>
@endif
</div>
<div class="form-group">
<label for="description">Description</label>
<textarea cols="5" rows="3" class="form-control @if($errors->has('description')) is-invalid @endif" name="description">{{ ('description') }}</textarea>
@if($errors->has('description'))
<div class="invalid-feedback">{{ $errors->first('description') }}</div>
@endif
</div>
<div class="form-group">
<div class="form-check">
<input type="checkbox" class="form-check-input @if($errors->has('description')) is-invalid @endif" name="is_active" value="1" checked>
<label class="form-check-label" for="is_active">
Active Post?
</label>
@if($errors->has('is_active'))
<div class="invalid-feedback">{{ $errors->first('is_active') }}</div>
@endif
</div>
</div>
</div>
<div class="card-footer">
<button class="btn btn-primary" type="submit">Save</button>
<a href="{{ route('posts.index') }}" class="btn btn-default">Cancel</a>
</div>
</div>
</form>
@endsection
Next, open PostController.php and amend the create function as below.
/* app > Http > Controllers > PostController.php */
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class PostController extends Controller
{
...
public function create()
{
return view('posts.create');
}
...
}
After that, open localhost:8000/posts/create to see the create post page.
# Validate & Submit Form
In the meantime, while submitting a form, let’s create validation first. In this case, we are using form requests for validation logic. We’ll use two separate files for the creating and updating form. Of course, you can do it into one file. So let’s create CreatePostRequest request creating a post. It’ll create a form request file in app > Http > Requests folder. Request file contains simple validation rules and custom messages. Keep in mind, the authorize method of the request file must return true while validating.
php artisan make:request CreatePostRequest
/* app > Http > Requests > CreatePostRequest.php */
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class CreatePostRequest extends FormRequest
{
public function authorize()
{
return true;
}
public function rules()
{
return [
'title' => 'required|max:255|unique:posts,title',
'description' => 'required'
];
}
public function messages()
{
return [
'title.required' => 'Please enter post title.',
'title.unique' => 'The post has already been taken.',
'description.required' => 'Please enter post description.'
];
}
}
To use this validation logic while submitting a form, open the store method of the PostController, and amend the following code. As you can see, there is no validation logic in the controller’s store method. It’s always a good idea to separate validation and business logic. To use validation, inject the request class as a parameter. As simple as that.
/* app > Http > Controllers > PostController.php */
<?php
namespace App\Http\Controllers;
use App\Http\Requests\CreatePostRequest;
use App\Models\Post;
use Illuminate\Http\Request;
class PostController extends Controller
{
...
public function store(CreatePostRequest $request)
{
Post::create([
'title' => $request->title,
'description' => $request->description,
'is_active' => isset($request->is_active) ? 1 : 0
]);
flash("Post created successfully");
return redirect()->route('posts.index');
}
...
}
After amending the store method of the PostController, add a fillable property with column names into the Post model.
/* app > Models > Post.php */
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
use HasFactory;
protected $fillable = ['title', 'description', 'is_active'];
}
# List Posts
In the same way, let’s get one step ahead and create a listing of the posts. To do that, open index method of the PostController and return index.blade.php view with all posts.
/* app > Http > Controllers > PostController.php */
<?php
namespace App\Http\Controllers;
use App\Http\Requests\CreatePostRequest;
use App\Models\Post;
use Illuminate\Http\Request;
class PostController extends Controller
{
...
public function index()
{
$posts = Post::paginate(10);
return view('posts.index', compact('posts'));
}
...
}
Most importantly, laravel 8 comes with tailwind css by default. If you want to use bootstrap pagination either, change the boot method of the AppServiceProvider.
/* app > Providers > AppServiceProvider.php */
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Illuminate\Pagination\Paginator;
class AppServiceProvider extends ServiceProvider
{
public function register()
{
}
public function boot()
{
Paginator::useBootstrap();
}
}
Now it’s time to create index.blade.php markup for listing all of our posts. As you can see, there are three links with edit, show and delete functionality. For deleting, I’ve used the bootstrap confirmation dialog. We’ll discuss edit, show and delete functionality later in this tutorial.
@extends('layouts.master')
@section('title') Posts @endsection
@section('content')
<div class="row">
<div class="col-md-12">
<a href="{{ route('posts.create') }}" class="btn btn-primary pull-right mb-3">Add New</a>
</div>
</div>
<div class="table-responsive">
<table class="table table-striped">
<thead>
<tr>
<th>#</th>
<th>Title</th>
<th>Description</th>
<th>Status</th>
<th>Created At</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@forelse($posts as $post)
<tr>
<td>{{ $loop->iteration }}</td>
<td>{{ $post->title }}</td>
<td>{{ $post->description }}</td>
<td>
@if($post->is_active == 1)
<span class="badge badge-pill badge-success p-2">Active</span>
@else
<span class="badge badge-pill badge-danger p-2">Inactive</span>
@endif
</td>
<td>{{ $post->created_at->diffForHumans() }}</td>
<td>
<div class="btn-group" role="group" aria-label="Basic example">
<a href="{{ route('posts.edit', $post->id) }}" type="button" class="btn btn-primary"><i class="fa fa-edit"></i></a>
<a href="{{ route('posts.show', $post->id) }}" type="button" class="btn btn-info"><i class="fa fa-eye"></i></a>
<button type="button" class="btn btn-danger" data-toggle="modal" data-target="#delete_post_{{ $post->id }}">
<i class="fa fa-trash"></i>
</button>
<div class="modal fade" id="delete_post_{{ $post->id }}" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<form action="{{ route('posts.destroy', $post->id) }}" id="form_delete_post_{{ $post->id }}" method="post">
@csrf
@method('DELETE');
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">Delete Confirmation</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
Are you sure want to delete "<b>{{ $post->title }}</b>" post?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">No</button>
<button type="submit" class="btn btn-danger">Yes! Delete It</button>
</div>
</div>
</form>
</div>
</div>
</div>
</td>
</tr>
@empty
<tr>
<td colspan="5" align="center">No Posts Found.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<div class="row">
{{ $posts->links() }}
</div>
@endsection
Open, localhost:8000/posts to see all of the posts.
# View Single Post
Now, It’s time to view a single post with an eye button from the posts listing page. To do that, update the PostController‘s show method and change the markup of show.blade.php. Clicking on the eye button will take you to the posts.show route with the specific post id.
/* app > Http > Controllers > PostController.php */
<?php
namespace App\Http\Controllers;
use App\Http\Requests\CreatePostRequest;
use App\Models\Post;
use Illuminate\Http\Request;
class PostController extends Controller
{
...
public function show(Post $post)
{
return view('posts.show', compact('post'));
}
...
}
/* resources > views > posts > show.blade.php */
@extends('layouts.master')
@section('title') View Post @endsection
@section('content')
<div class="row">
<div class="col-md-12">
<a href="{{ route('posts.index') }}" class="btn btn-info pull-right mb-3">Back</a>
</div>
</div>
<div class="table-responsive">
<table class="table table-striped">
<tr>
<th>Title</th>
<td>{{ $post->title }}</td>
</tr>
<tr>
<th>Description</th>
<td>{{ $post->description }}</td>
</tr>
<tr>
<th>Status</th>
<td>
@if($post->is_active == 1)
<span class="badge badge-pill badge-success p-2">Active</span>
@else
<span class="badge badge-pill badge-danger p-2">Inactive</span>
@endif
</td>
</tr>
<tr>
<th>Created At</th>
<td>{{ $post->created_at->diffForHumans() }}</td>
</tr>
</table>
</div>
@endsection
# Edit Post
To edit the post let’s modify edit.blade.php as shown below. After that, modify PostController‘s edit method.
@extends('layouts.master')
@section('title') Edit Post @endsection
@section('content')
<form action="{{ route('posts.update', $post->id) }}" method="post">
@csrf
@method('PUT')
<div class="card card-default">
<div class="card-header">
Create Post
</div>
<div class="card-body">
<div class="form-group">
<label for="title">Title</label>
<input type="text" class="form-control @if($errors->has('title')) is-invalid @endif" name="title"
value="{{ $post->title }}">
@if($errors->has('title'))
<div class="invalid-feedback">{{ $errors->first('title') }}</div>
@endif
</div>
<div class="form-group">
<label for="description">Description</label>
<textarea cols="5" rows="3" class="form-control @if($errors->has('description')) is-invalid @endif"
name="description">{{ $post->description }}</textarea>
@if($errors->has('description'))
<div class="invalid-feedback">{{ $errors->first('description') }}</div>
@endif
</div>
<div class="form-group">
<div class="form-check">
<input type="checkbox"
class="form-check-input @if($errors->has('description')) is-invalid @endif"
name="is_active" value="1" @if($post->is_active == 1) checked @endif>
<label class="form-check-label" for="is_active">
Active Post?
</label>
@if($errors->has('is_active'))
<div class="invalid-feedback">{{ $errors->first('is_active') }}</div>
@endif
</div>
</div>
</div>
<div class="card-footer">
<button class="btn btn-primary" type="submit">Update</button>
<a href="{{ route('posts.index') }}" class="btn btn-default">Cancel</a>
</div>
</div>
</form>
@endsection
/* app > Http > Controllers > PostController.php */
<?php
namespace App\Http\Controllers;
use App\Http\Requests\CreatePostRequest;
use App\Http\Requests\EditPostRequest;
use App\Models\Post;
use Illuminate\Http\Request;
class PostController extends Controller
{
...
public function edit(Post $post)
{
return view('posts.edit', compact('post'));
}
...
}
# Validate Posts While Editing
As shown above, our application is crafted step by step. Likewise, create functionality we also need form request for validation. You need to pass an id of the post for validation when updating a post. It will ignore the title for the current post while validating the title.
php artisan make:request EditPostRequest
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class EditPostRequest extends FormRequest
{
public function authorize()
{
return true;
}
public function rules()
{
return [
'title' => 'required|max:255|unique:posts,title,'.$this->post->id,
'description' => 'required'
];
}
public function messages()
{
return [
'title.required' => 'Please enter post title.',
'title.unique' => 'The post has already been taken.',
'description.required' => 'Please enter post description.'
];
}
}
# Update Post
Similar to creating a post, updating a post is easy too. Let’s inject EditPostRequest form request for validation in PostController‘s update method.
/* app > Http > Controllers > PostController.php */
<?php
namespace App\Http\Controllers;
use App\Http\Requests\CreatePostRequest;
use App\Http\Requests\EditPostRequest;
use App\Models\Post;
use Illuminate\Http\Request;
class PostController extends Controller
{
...
public function update(EditPostRequest $request, Post $post)
{
$post->update([
'title' => $request->title,
'description' => $request->description,
'is_active' => isset($request->is_active) ? 1 : 0
]);
flash("Post updated successfully");
return redirect()->route('posts.index');
}
...
}
# Delete Post
Additionally, deleting a post is necessary too. We already put a confirmation dialog in index.blade.php with proper routing. You need to modify PostController‘s destroy function.
/* app > Http > Controllers > PostController.php */
<?php
namespace App\Http\Controllers;
use App\Http\Requests\CreatePostRequest;
use App\Http\Requests\EditPostRequest;
use App\Models\Post;
use Illuminate\Http\Request;
class PostController extends Controller
{
...
public function destroy(Post $post)
{
$post->delete();
flash("Post Deleted Successfully");
return redirect()->back();
}
...
}
I hope that you like this tutorial for Laravel 8 CRUD. There are many more tutorials to learn. Please support with comment and share. Thank you.
All the things is explained easy and in a fair way.
Happy to help 🙂
all things same as you but not working edit update and delete
COol bro