Laravel 10 CRUD app using Bootstrap (Laravel UI)

In this tutorial we’ll create Laravel 10 CRUD app having Bootstrap based frontend by employing classic Laravel UI package. Laravel 10.x requires a minimum PHP version of 8.1. This tutorial assumes that you are using Linux, MacOS or WSL on Windows and Node.js, Composer and PHP 8.1 along with required modules i.e bcmatch, sqlite, mbstring, xml, zip, gd, mcrypt, curl are properly installed. Also make sure that SQLite is installed as we’ll be using SQLite to keep things simple and quick; you may use MySQL or PostgreSQL as you like, however the same will require the corresponding PHP module installed too.

Using Composer, enter following command in terminal to install Laravel 10:

composer create-project laravel/laravel app 10.*

Go inside app folder:

cd app

Then add Laravel/UI package by entering following command:

composer require laravel/ui:*

Then install frontend scaffolding using Bootstrap:

php artisan ui bootstrap --auth

Install and build:

npm install && npm run build

Next, setup database and Laravel environment file. Here, we’re using SQLite database for simplicity purposes. Being inside parent folder (app), execute following command to create new SQLite database file:

touch database/database.sqlite

Then find .env config file in the parent folder (app), open it and change DB_CONNECTION line as follows:

DB_CONNECTION = sqlite

Remove DB_HOST, DB_PORT, DB_DATABASE, DB_USERNAME, DB_PASSWORD from .env since we’re using SQLite database.

Initial setup of Laravel app is done! we can now start building the CRUD. First enter the following command to create Post model along with the migration and controller:

php artisan make:model Post -mrc

Open database/migrations/(time_stamp)_create_posts_table.php and then insert the table fields as follows:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->text('body');
            $table->timestamps();
        });
    }
    public function down(): void
    {
        Schema::dropIfExists('posts');
    }
};

Enter following command to populate the database with tables:

php artisan migrate

Next, in order to enable mass-assignment for relevant data fields, open app/Models/Post.php model file and insert protected fillable array as follows:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    use HasFactory;

    protected $fillable = [
        'title', 'body'
    ];
}

Then, open app/Http/Controllers/PostController.php and amend it as follows in order to define basic CRUD (Create, Read, Update, Delete) functions:

<?php

namespace App\Http\Controllers;

use App\Models\Post;
use Illuminate\Http\Request;
use Illuminate\View\View;
use Illuminate\Http\RedirectResponse;

class PostController extends Controller
{

    public function index(): View
    {
        $posts = Post::all();
        return view('posts.index', compact('posts'));
    }

    public function create(): View
    {
        return view('posts.create');
    }

    public function store(Request $request): RedirectResponse
    {
        $validated = $request->validate([
            'title' => ['required', 'max:50'],
            'body' => ['required'],
        ]);
        $item = Post::create($validated);
        return redirect(route('posts.index'))->with('success', 'Post created.');
    }

    public function show(Post $post): View
    {
        //
    }

    public function edit(Post $post): View
    {
        return view('posts.edit', compact('post'));
    }

    public function update(Request $request, Post $post): RedirectResponse
    {
        $validated = $request->validate([
            'title' => ['required', 'max:50'],
            'body' => ['required'],
        ]); 
        $post->update($validated);
        return redirect(route('posts.index'))->with('success', 'Post updated.');
    }

    public function destroy(Post $post): RedirectResponse
    {
        $post->delete();
        return redirect(route('posts.index'))->with('success', 'Post deleted.');
    }

}

In order to define resource route, open routes/web.php and put the following line at the upper side of it:

use App\Http\Controllers\PostController;

And then, insert following resource route down somewhere into this routes/web.php:

Route::resource('posts', PostController::class)->middleware(['auth', 'verified']);

We’re done with the Model and Controller parts of the app. Now let’s create Views using Blade templates.

First, create posts folder inside resources/views. Being inside parent folder, you may execute following command to make this folder:

mkdir resources/views/posts 

Now create following three files inside resources/views/posts folder:

  1. index.blade.php
  2. create.blade.php
  3. edit.blade.php

You can execute following command inside parent folder to create these files in one go:

touch resources/views/posts/{index,create,edit}.blade.php

Go inside resources/views/posts folder and insert following code into index.blade.php:

@extends('layouts.app')

@section('content')
    <div class="row">
        <div class="ms-4">
            <div>
                <h2>POSTS</h2>
            </div>
            <div>
                <a class="btn btn-sm btn-primary" href="{{ route('posts.create') }}">Create New Post</a>
            </div>
        </div>
    </div>

    @if ($message = session('success'))
        <div class="alert alert-success alert-dismissible fade show m-2" role="alert">
            {{ $message }}
            <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
        </div>
    @endif

    <div class="card m-4">
        <div class="card-body">
            <table class="table table-hover">
                <tr>
                    <th width="10%">S.no.</th>
                    <th>Title</th>
                    <th>Body</th>
                    <th width="20%">Actions</th>
                </tr>
                <?php $i = 0; ?>
                @foreach ($posts as $post)
                <tr>
                    <td>{{ ++$i }}</td>
                    <td>{{ $post->title }}</td>
                    <td>{{ $post->body }}</td>
                    <td class="d-flex justify-content-around">
                        <a class="btn btn-sm btn-secondary" href="{{ route('posts.edit', $post->id) }}">Edit</a>
                        <form action="{{ route('posts.destroy', $post->id) }}" method="POST">
                            @csrf
                            @method('DELETE')
                            <button type="submit" class="btn btn-sm btn-danger">Delete</button>
                        </form>
                    </td>
                </tr>
                @endforeach
            </table>
        </div>
    </div>
@endsection

Then, insert following code into create.blade.php:

@extends('layouts.app')

@section('content')
<div class="card m-4">
  <div class="card-header">
    <h4>Create Post</h4>
  </div>
  <div class="card-body">
    @if ($errors->any())
      <div class="alert alert-danger">
        <ul>
            @foreach ($errors->all() as $error)
              <li>{{ $error }}</li>
            @endforeach
        </ul>
      </div>
    @endif
    <form method="post" action="{{ route('posts.store') }}">
        @csrf
        <div class="form-group">
            <label for="title">Title</label>
            <input type="text" class="form-control form-control-sm" name="title"/>
        </div>
        <div class="form-group">
            <label for="body">Body</label>
            <textarea class="form-control form-control-sm" name="body"></textarea>
        </div>
        <button type="submit" class="btn btn-sm btn-secondary mt-4">Add Post</button>
    </form>
  </div>
</div>
@endsection

And then, insert following code into edit.blade.php:

@extends('layouts.app')

@section('content')
<div class="card m-4">
  <div class="card-header">
    <h4>Edit Post</h4>
  </div>
  <div class="card-body">
    @if ($errors->any())
      <div class="alert alert-danger">
        <ul>
            @foreach ($errors->all() as $error)
              <li>{{ $error }}</li>
            @endforeach
        </ul>
      </div>
    @endif
    <form action="{{ route('posts.update', $post->id) }}" method="POST">
        @csrf
        @method('PUT')
        <div class="form-group">
            <label for="title">Title</label>
            <input type="text" class="form-control form-control-sm" name="title" value="{{ $post->title }}"/>
        </div>
        <div class="form-group">
            <label for="body">Body</label>
            <textarea class="form-control form-control-sm" name="body">{{ $post->body }}</textarea>
        </div>
        <button type="submit" class="btn btn-sm btn-secondary mt-4">Submit</button>
    </form>
  </div>
</div>
@endsection

Finally, enter following command to start PHP dev server:

php artisan serv

Enter following URL in the browser and test CRUD functions (Don’t forget to create a user login first):

http://localhost:8000/posts

Leave a Comment