Laravel 8 CRUD Step By Step Tutorial

4
4169
Laravel 8 CRUD Step By Step Tutorial

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.

# 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');
Laravel 8 CRUD Resource Routing

# 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.

Laravel 8 CRUD Post Create

# 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.

Laravel 8 CRUD - Listing 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
Laravel 8 CRUD - View Single Post

# 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.

Learn more about Laravel Framework

Previous articleVuejs Custom Events To Emit Data
Next articleFirebase Push Notification Using Laravel
Welcome to the world of web development, where technology and creativity come together to bring ideas to life. My name is Keyur Gadher, and as a web development blogger, I am here to share my knowledge and experience with you. From front-end web development to back-end programming, I will provide you with valuable tips, tricks, and techniques to help you create beautiful and functional websites that are tailored to your specific needs. Whether you're a beginner or a seasoned pro, I am here to help you take your web development skills to the next level. Join me today and let's start coding!

4 COMMENTS

LEAVE A REPLY

Please enter your comment!
Please enter your name here