Laravel 9: Building a blog application with Bootstrap 5
Exploring the features of Laravel 9 by building a blog application using bootstrap 5
In this article, we'll look into the new laravel version and use it to build a blog application (create, read, update and delete) with bootstrap 5.
Laravel is the most widely used and popular PHP framework for building custom, robust, and scalable web applications.
The Laravel 9 was released on the 8th of February 2022 by the awesome Laravel team led by Taylor Otwell with minimum support for PHP 8.0 - 8.1.
Laravel v9 is the first LTS(Long-Term Support) to be introduced following the 12 months release cycle and will be stable for 12 months until another release most likely on January 24th, 2023.
Laravel 9 continues the improvements made in Laravel 8.x by introducing support for Symfony 6.0 components, Symfony Mailer, Flysystem 3.0, improved route:list output, a Laravel Scout database driver, new Eloquent accessor/mutator syntax, implicit route bindings via Enums, and a variety of other bug fixes and usability improvements. source
Here's the link to the laravel release note
In a bit to get on board with some of the features in Laravel 9, we'll be building a simple blog application.
Let's start with a fresh laravel 9 app installation
laravel new laravel9_blog_application
OR
composer create-project laravel/laravel9_blog_application
After successful installation change the directory to laravel9_blog_application
cd laravel9_blog_application
--Run
php artisan --version
Yoo!!! Laravel 9 app created
php artisan serve
Awesome
**Use the version constraint such as ^9.0 in referencing the laravel framework or components since major releases do include breaking changes. **
Next, let's set the environment variable for this blog application on the .env file
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel9_blog_application
DB_USERNAME=root
DB_PASSWORD=
Before we run migrate let's look at one of the new features in laravel 9
Anonymous Stub Migrations
The Laravel team released Laravel 8.37 with anonymous migration support, which solves a GitHub issue with migration class name collisions. The core of the problem is that if multiple migrations have the same class name, it'll cause issues when trying to recreate the database from scratch.
As you may have noticed in the example below, Laravel will automatically assign a class name to the migration that you generate using the make:migration command. However, if you wish, you may return an anonymous class from your migration file. This is primarily useful if your application accumulates many migrations and two of them have a class name collision:
The stub migration feature eliminates migration class name collisions when you run php artisan make:migration
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{}
/**
* Reverse the migrations.
*
* @return void
*/
public function down(){ }
};
Next, run the migrate command
php artisan migrate
Install npm and Bootstrap 5 dependencies using the npm command below
npm install
npm install --save bootstrap jquery popper.js cross-env
Import packages in the resources/js/bootstrap.js file
try {
window.Popper = require('popper.js').default;
window.$ = window.jQuery = require('jquery');
require('bootstrap');
} catch (e) {
}
Rename the css folder in the resource folder to sass
Import packages in the resources/sass/app.scss file
// bootstrap
@import "~bootstrap/scss/bootstrap";
Open the webpack.mix.js and update it
mix.js('resources/js/app.js', 'public/js')
.sass('resources/sass/app.scss', 'public/css');
Next, compile the installed assets
npm run dev
Kindly note: if this error occurs
ERROR in ./node_modules/bootstrap/dist/js/bootstrap.esm.js 6:0-41 Module not found: Error: Can't resolve '@popperjs/core' in
Run the command below and recompile
npm i @popperjs/core --save
Next, we'll add a basic authentication feature (Login and register) using laravel fortify.
The process for setting it up in Laravel 9 is the same as this article I published last year on Complete Laravel 8 Authentication Using Laravel Fortify and Bootstrap 4.
Laravel Fortify
Laravel Fortify is a frontend agnostic authentication backend implementation for Laravel. Fortify registers the routes and controllers needed to implement all of Laravel's authentication features, including login, registration, password reset, email verification, and more.
Laravel Fortify essentially takes the routes and controllers of Laravel Breeze and offers them as a package that does not include a user interface. This allows you to still quickly scaffold the backend implementation of your application's authentication layer without being tied to any particular frontend opinions. Read more about fortify
We're using fortify because, the Laravel UI in previous versions is still available with a new version but it's advisable that users migrate to recommended authentication features such as Jetstream, Breeze or Fortify because it might be removed in subsequent versions.
composer require laravel/fortify
Next, publish Fortify's resources using the vendor:publish command:
php artisan vendor:publish --provider="Laravel\Fortify\FortifyServiceProvider"
Register the file within the provider array of your app configuration file
App\Providers\FortifyServiceProvider::class,
In this article, we'll stop at adding the auth views and setting up the register, verify and login logic only. You can get sample auth views from this repo or use your bootstrap custom snippets in each file as desired.
In the root of resources/views folder create auth folder layouts folder & home.blade.php
create the following files in the folders respectively
resources/views/layouts/app.blade.php
resources/views/auth/login.blade.php
resources/views/auth/register.blade.php
Add the following methods to Fortify Provider file
app/Providers/FortifyServiceProvider
Fortify::loginView(function () {
return view('auth.login');
});
Fortify::registerView(function () {
return view('auth.register');
});
We're simply telling fortify to render the views we've set up using these boot methods
Here's a screenshot of the login and register pages
- Login page
- Register
Improved Ignition Exception Page in Laravel 9
Ignition is developed by Spatie.
Ignition, the open-source exception debug page created by Spatie, has been redesigned from the ground up. The new, improved Ignition ships with Laravel 9.x and includes light / dark themes, customizable "open in editor" functionality, and more.
Here's an example of an exception page
Improved route:list CLI Output in Laravel 9
Improved route:list CLI output was contributed by Nuno Maduro.
The route:list CLI output has been significantly improved for the Laravel 9.x release, offering a beautiful new experience when exploring your route definitions.
Let's add email verification using mailtrap
Symfony Mailer support was contributed by Dries Vints, James Brooks, and Julius Kiekbusch.
In Laravel 9 Swift Mailer is no longer maintained and has been succeeded by Symfony mailer.
Let's look at the email verification feature .. uncomment the feature of the email verification method in fortify.php file
Features::emailVerification(),
it'll automatically add the three routes for email verification (send, notice and verify)
Add this to the fortify service provider
Fortify::verifyEmailView(function () {
return view('auth.verify');
});
Next, go to User.php model under the Models folder and modify it by implementing the MustVerifyEmail interface
Next, configure your preferred email driver to deliver the email verification link to the user mail.
For testing purposes, you can create an account on mailtrap.io and configure the mail settings on the .env file
MAIL_DRIVER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=
MAIL_PASSWORD=
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS="your@gmail.com"
MAIL_FROM_NAME="${APP_NAME}"
Next, in web.php file specify the routes that are not accessible until a user is verified
Route::middleware(['auth', 'verified'])->group(function () {
//define the routes accessible only to verified user here
});
Next refresh the browser. Register as a user. Check the mailtrap inbox to see the mail for verifying the email.
Awesome!!! Everything works fine even with the new Swift Mailer
If you want to go further in using the other features of Fortify kindly check out this article
Next, let's create a posts table to keep a record of posts for our blog
We'll use a free blog template from startbootstrap for some of the posts pages
php artisan make:migration create_posts_table --create=posts
Add the following columns
$table->foreignId('user_id');
$table->string('title');
$table->text('description');
php artisan migrate
Next, let's create the posts folder and the index, show, create and edit blade file. The home view will be used to list all the posts.
Next, we'll create a resource controller and model for Posts named PostController.
php artisan make:controller PostController --resource --model=Post
Here's the PostController. We have an additional method called all_posts to display all posts
<?php
namespace App\Http\Controllers;
use App\Models\Post;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class PostController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
$posts = Post::where('user_id', Auth::user()->id)->latest()->get();
return view('posts.index', compact('posts'));
}
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
return view('posts.create');
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$request->validate([
'title' => 'required|string|max:255',
'description' => 'required|string',
]);
$post = new Post($request->all());
$user = Auth::user();
$user->posts()->save($post);
return redirect()->route('posts.create')
->with('success','Post created successfully.');
}
/**
* Display the specified resource.
*
* @param \App\Models\Post $post
* @return \Illuminate\Http\Response
*/
public function show(Post $post)
{
return view('posts.show',compact('post'));
}
/**
* Show the form for editing the specified resource.
*
* @param \App\Models\Post $post
* @return \Illuminate\Http\Response
*/
public function edit(Post $post)
{
return view('posts.edit', compact('post'));
}
/**
* Update the specified resource in storage.
*
* @param \Illuminate\Http\Request $request
* @param \App\Models\Post $post
* @return \Illuminate\Http\Response
*/
public function update(Request $request, Post $post)
{
$request->validate([
'title' => 'required|string|max:255',
'description' => 'required|string',
]);
$post->update($request->all());
return redirect()->route('posts.index')
->with('success','Post updated successfully');
}
/**
* Remove the specified resource from storage.
*
* @param \App\Models\Post $post
* @return \Illuminate\Http\Response
*/
public function destroy(Post $post)
{
$post->delete();
return redirect()->route('posts.index')
->with('success','Post deleted!!!');
}
public function all_posts()
{
$posts = Post::latest()->get();
return view('home', compact('posts'));
}
}
Post Model
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Casts\Attribute;
class Post extends Model
{
use HasFactory;
protected $fillable = [
'title', 'description'
];
//relationship to retrieve the user associated with a post
public function user()
{
return $this->belongsTo(User::class);
}
//new laravel 9 single non-prefix method
public function created_at(): Attribute
{
return new Attribute(
get: fn ($value) => $value->diffForHumans(),
// set: fn ($value) => $value,
);
}
}
In Laravel 9.x you may define an accessor and mutator using a single, non-prefixed method by type-hinting a return type of Illuminate\Database\Eloquent\Casts\Attribute:
use Illuminate\Database\Eloquent\Casts\Attribute;
//let's format the created_at timestamp to be readable by readers
public function created_at(): Attribute
{
return new Attribute(
get: fn ($value) => $value->diffForHumans(),
//set: fn ($value) => $value, you can also set the value when creating the record
);
}
let's update the user model to associate posts to the author(user)
public function posts()
{
return $this->hasMany(Post::class);
}
Route setup on web.php
Laravel 9 Controller Route Groups
In Laravel 9, you may now use the controller method to define the common controller for all of the routes within the group. Then, when defining the routes, you only need to provide the controller method that they invoke:
<?php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\PostController;
//Controller Route Groups
Route::controller(PostController::class)->group(function () {
Route::get('/', 'all_posts');
Route::get('/all/posts', 'all_posts')->name('home');
});
Route::middleware(['auth', 'verified'])->group(function () {
Route::resource('posts', PostController::class);
});
Post pages (index, create, edit, show), Including form validations, Including the home & welcome page views are available on this tutorial repository
The tutorial repository contains the views.
Welcome Page
Add post
User's blog posts
Show post details
If logged in user is the author show edit and delete
In real-world applications ensure to make use of the soft delete for a delete action and also confirm the action from the user just in a case of unintended click.
Support for String Functions in PHP 8
The most recent string functions str_contains(), str_starts_with(), and str_ends_with() internally in the IlluminateSupportStr class are incorporated for use in laravel 9.
You got to this point awesome!!! Congratulations!!! You can now build an app of your choice using laravel 9 with bootstrap 5.
Conclusion
In this article, we've covered some new features introduced in Laravel 9. Worthy of note is that there's no difficulty in migrating to Laravel 9 from previous Laravel versions.
Find this helpful or resourceful?? kindly share and feel free to use the comment section for questions, answers, and contributions.