Laravel 10 Crash Course For Beginners.

Laravel 10 Crash Course For Beginners

Welcome to the Laravel 10 crash course for beginners! In this tutorial, we will be creating a fully functional blogging system from scratch using Laravel 10, one of the most popular PHP frameworks. Throughout this tutorial, we will cover all the basic concepts of Laravel, including routing, views, sessions, logging, and many more. We will begin with a brief introduction to Laravel and its key features. Then, we will jump straight into building our blogging system, step by step.

In this tutorial, you will learn how to create and manage blog posts, handle user authentication and authorization, and implement a dynamic theme toggle feature that allows users to switch between a light and dark theme. Whether you are new to Laravel or looking to expand your skills, this tutorial will provide you with the knowledge and skills to build a complete Laravel application from start to finish. So, let’s get started and create our very own blogging system using Laravel 10!

Installing Laravel 10 on Windows and Linux.

Let’s start by installing Laravel on Windows using the following steps…

On your terminal, you can run the following command

composer create-project laravel/laravel My-Blog

This command assumes you have installed PHP and Composer. Or you can use the global installer via composer to install Laravel Projects.

composer global require laravel/installer
 
laravel new My-Blog

Finally

cd My-Blog
 
php artisan serve

For Linux users. You may do it this way…

  1. Open a terminal window on your Ubuntu system.
  2. Install PHP and its required extensions using the following command:
sudo apt-get update
sudo apt-get install php php-cli php-common php-json php-mbstring php-zip php-gd php-mysql

3. Install Composer, which is a dependency manager for PHP:

sudo apt-get install curl
curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer

4. Install the latest version of Laravel 10 by running the following command:

composer global require laravel/installer

5. Add the Composer binary directory to your PATH environment variable:

echo 'export PATH="$HOME/.config/composer/vendor/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc

6. Navigate to the directory where you want to install Laravel 10 and create a new Laravel project using the following command:

laravel new My-Blog

7. Change the directory to the newly created project folder:

cd My-Blog
code .

1. Routing

In Laravel 10, routing refers to the process of defining URLs (also called routes) for your application and mapping them to specific controller actions.

Routing is essential to create a RESTful architecture and define the entry points of your application.

Here is an example of a basic route definition in Laravel 10:

Route::get('/hello', function () {
return 'Hello, World!';
});

This route maps the /hello URL to an anonymous function that returns a simple string.

You can also define routes that point to specific controller actions:

Route::get('/posts', [PostController::class, 'index']);

This route maps the /posts URL to the index method of the PostController class.

Laravel 10 routing allows you to define various types of HTTP methods like GET, POST, PUT, DELETE, PATCH, and others,

as well as route parameters, middleware, named routes, route groups, and more.

Here’s another example that uses route parameters:

Route::get('/users/{id}', function ($id) {
    return "Showing user with ID: $id";
});

Now let’s implement Routing in our blogging system. We will start with the admin side.

web.php

Here we will create different routes that will point to specific controller actions:

// Backend
Route::get('/admin/posts', 'App\Http\Controllers\Admin\PostController@index')->name('admin.posts.index');

Route::get('/admin/posts/create', 'App\Http\Controllers\Admin\PostController@create')->name('admin.posts.create');

Route::post('/admin/posts/store', 'App\Http\Controllers\Admin\PostController@store')->name('admin.posts.store');

Route::get('/admin/posts/{post}/edit', 'App\Http\Controllers\Admin\PostController@edit')->name('admin.posts.edit');

Route::patch('/admin/posts/{post}', 'App\Http\Controllers\Admin\PostController@update')->name('admin.posts.update');

Route::delete('/admin/posts/{post}', 'App\Http\Controllers\Admin\PostController@destroy')->name('admin.posts.destroy');

Explanations…

Route::get(‘/admin/posts’, ‘App\Http\Controllers\Admin\PostController@index’) >name(‘admin.posts.index’);: This route maps to the index method in the PostController class, which displays a list of all posts in the admin panel.

Route::get(‘/admin/posts/create’, ‘App\Http\Controllers\Admin\PostController@create’)->name(‘admin.posts.create’);: This route maps to the create method in the PostController class, which displays a form for creating a new post.

Route::post(‘/admin/posts/store’, ‘App\Http\Controllers\Admin\PostController@store’)->name(‘admin.posts.store’);: This route maps to the store method in the
PostController class, which handles the submission of the create post form.

Route::get(‘/admin/posts/{post}/edit’, ‘App\Http\Controllers\Admin\PostController@edit’)->name(‘admin.posts.edit’);: This route maps to the edit method in the PostController class, which displays a form for editing an existing post. The {post} parameter is a wildcard that matches the ID of the post being edited.

Route::patch(‘/admin/posts/{post}’, ‘App\Http\Controllers\Admin\PostController@update’)->name(‘admin.posts.update’);: This route maps to the update method in the PostController class, which handles the submission of the edit post form. The {post} parameter is a wildcard that matches the ID of the post being updated.

Route::delete(‘/admin/posts/{post}’, ‘App\Http\Controllers\Admin\PostController@destroy’)->name(‘admin.posts.destroy’);: This route maps to the destroy method in the PostController class, which handles the deletion of a post. The {post} parameter is a wildcard that matches the ID of the post being deleted.

Let’s also create Routes for the Frontend side

// Frontend
Route::get('/', 'App\Http\Controllers\PostController@insdex')->name('posts.index');
Route::get('/posts/{post}', 'App\Http\Controllers\PostController@show')->name('posts.show');

// Comments
Route::post('/posts/{post}/comments', 'App\Http\Controllers\CommentController@store')->name('comments.store');

Explanations…

The first route Route::get(‘/’, ‘App\Http\Controllers\PostController@index’)->name(‘posts.index’) maps the root URL / to the index method of the PostController class. The route name is posts.index.

The second route Route::get(‘/posts/{post}’, ‘App\Http\Controllers\PostController@show’)->name(‘posts.show’) maps the URL /posts/{post} to the show method of the PostController class. The route name is posts.show. The {post} parameter is a route parameter that captures the post ID or slug and passes it to the show method.

The third route Route::post(‘/posts/{post}/comments’, ‘App\Http\Controllers\CommentController@store’)->name(‘comments.store’) maps the URL /posts/{post}/comments to the store method of the CommentController class. The route name is comments.store. The {post} parameter is a route parameter that captures the post ID or slug to which the comment belongs.

Now these Routes are pointing to specific Controllers. So we need to create them. So let’s look at controllers in Laravel 10.

2. Controllers.

In Laravel 10, a controller is a PHP class that handles incoming HTTP requests, processes them, and returns an appropriate response to the client.

From the above Routes start with the admin Routes. Let’s create controllers for them. Create an Admin folder in the controllers. ie

app/http/controllers/Admin/…and then PostController.php

app/http/controllers/Admin/PostController.php

namespace App\Http\Controllers\Admin;

use App\Models\Post;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Storage;
use App\Http\Requests\CreatePostRequest;

class PostController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\Http\Response
*/
public function index()
{
    $posts = Post::all();
    return view('admin.posts.index', compact('posts'));
}

/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\Response
*/
public function create()
{
   return view('admin.posts.create');
}

/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(CreatePostRequest $request)
{
 // using Laravel validation

   $validatedData = request()->validate([
   'title' => 'required|string|max:255',
   'body' => 'required|string',
   'image' => 'required|image|mimes:jpeg,png,jpg,gif,svg|max:2048',
   ]);

   $imagePath = request('image')->store('public/images');

   $post = new Post;
   $post->title = $validatedData['title'];
   $post->body = $validatedData['body'];
   $post->image = str_replace('public/', '', $imagePath);
   $post->user_id = auth()->user()->id;
   $post->save();

   return redirect()->route('admin.posts.index')->with('message', 'Your post has been updated!');

}

/**
* Display the specified resource.
*
* @param \App\Models\Post $post
* @return \Illuminate\Http\Response
*/
public function show($id)
{
   $post = Post::findOrFail($id);

   return view('admin.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('admin.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|max:255',
  'body' => 'required',
  'image' => 'nullable|image|mimes:jpeg,png,jpg,gif|max:2048' // optional validation rule for image upload
]);

  $post->title = $request->title;
  $post->body = $request->body;

if ($request->hasFile('image')) {
// delete the old image if it exists
if ($post->image && Storage::exists($post->image)) {
Storage::delete($post->image);
}

 //store the new image and set its path to the post's image field
   $imagePath = $request->file('image')->store('public/images');
   $post->image = str_replace('public/', '', $imagePath); // set the image path to the post's image field
}

   $post->save();

   return redirect()->route('admin.posts.index')->with('message', 'Your post has been updated!');

}

/**
* 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('admin.posts.index')->with('message', 'Your post has been deleted!');

}
}

This controller serves different things such as creating our posts, editing a post, and updating and deleting a post. There is validation. Before the post gets created. Validation ensures the correct details are submitted to the database. Let’s also illustrate laravel requests.

3. Laravel Requests

Requests are an important feature in Laravel that enable you to validate incoming data before processing it. In the context of a blog, you can use requests to validate the data that users enter when creating or updating a blog post or a comment. Here’s how you can use requests in your Laravel blog:

a) Create a new request class using the php artisan make:request command. For example, to create a request class for creating a new blog post, you can run the following command:

php artisan make:request CreatePostRequest

This will create a new CreatePostRequest class in the app/Http/Requests directory.

b). Open the newly created request class and define the validation rules for the request in the rules method. For example, to require that the title and body fields are present and that the title field is no more than 255 characters long, you can define the following rules:

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class CreatePostRequest extends FormRequest
{
public function authorize()
{
return true; // allow all users to use this request
}

public function rules()
{
   return [ 
  'title' => 'required|max:255',
  'body' => 'required',
  'image' => 'required|image|mimes:jpeg,png,jpg,gif|max:2048', // add validation rules for the image
  ];
}
}

And in your controller, you can then use the validated() method to retrieve the validated data from the request, like this:

public function store(CreatePostRequest $request)
{
   $data = $request->validated();

  // Create new blog post using $data['title'] and $data['body']

$imagePath = $request->file('image')->store('public/images'); // store the image in the "public/images" folder

$post = new Post;
$post->title = $data['title'];
$post->body = $data['body'];
$post->image = str_replace('public/', '', $imagePath); // set the image path to the post's image field
$post->user_id = auth()->user()->id;
$post->save();

return redirect()->route('admin.posts.index')->with('message', 'Your post has been updated!');

}

This way, the validation logic is moved to the form request class, making the controller code more readable and maintainable.

After we need the views to render our data to the browser.

4. Views

In Laravel 10, views are the user interface part of an application that presents data to the user. Views in Laravel are typically stored in the resources/views directory and can be created using the Blade templating engine.

In our app, we will have the following views on the admin side and later front end. Create two folders Admin and posts on the resources/views/Admin/posts and inside the folder posts create these three files. Index.blade.php, Create.blade.php and Edit.blade.php

resources/views/Admin/posts/create.blade.php

<x-app-layout>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8 col-md-offset-2">
@if(session()->has('message'))
<div x-data="{ show: true }" x-init="setTimeout(() => show = false, 5000)" x-show="show" class="bg-green-500 text-white py-2 px-4 rounded">
{{ session('message') }}
</div>
@endif
<div class="panel panel-default">
<div class="panel-heading">Create New Post</div>
<div class="panel-body">
<form class="form-horizontal" method="POST" action="{{ route('admin.posts.store') }}" enctype="multipart/form-data">
{{ csrf_field() }}

<div class="form-group{{ $errors->has('title') ? ' has-error' : '' }}">
<label for="title" class="col-md-4 control-label">Title</label>

<div class="col-md-6">
<input id="title" type="text" class="form-control" name="title" value="{{ old('title') }}" required autofocus>

@if ($errors->has('title'))
<span class="help-block">
<strong>{{ $errors->first('title') }}</strong>
</span>
@endif
</div>
</div>

<div class="form-group">
<label for="image">Image</label>
<input type="file" class="form-control-file" id="image" name="image">
</div>


<div class="form-group{{ $errors->has('body') ? ' has-error' : '' }}">
<label for="body" class="col-md-4 control-label">Body</label>

<div class="col-md-6">
<textarea id="body" class="form-control" name="body" rows="5" required>{{ old('body') }}</textarea>

@if ($errors->has('body'))
<span class="help-block">
<strong>{{ $errors->first('body') }}</strong>
</span>
@endif
</div>
</div>

<div class="form-group">
<div class="col-md-6 col-md-offset-4">
<button type="submit" class="btn btn-primary">
Create
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</x-app-layout>

This form is used for creating a post within our app it has a route that goes to admin.posts.store to create the post in our database. Remember the routes are defined in the web.php.

resources/views/Admin/posts/edit.blade.php

<x-app-layout>

@if(session('success'))
<div class="alert alert-success">
{{ session('success') }}
</div>
@endif

<div class="container">
<div class="row justify-content-center">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">Edit Post</div>

<div class="panel-body">
<form action="{{ route('admin.posts.update', $post->id) }}" method="post" enctype="multipart/form-data">
@csrf
@method('patch')
<div class="form-group">
<label for="title">Title</label>
<input type="text" class="form-control" name="title" id="title" value="{{ $post->title }}">
</div>
<div class="form-group">
<label for="body">Body</label>
<textarea class="form-control" name="body" id="body" rows="5">{{ $post->body }}</textarea>
</div>
<div class="form-group">
<label for="image">Image</label>
<input type="file" class="form-control-file" name="image" id="image">
</div>

<button type="submit" class="btn btn-primary">Update</button>
</form>
</div>
</div>
</div>
</div>
</div>
</x-app-layout>

This is the edit file that returns an Id of a post where you can edit a post and update it. So the route it goes to admin.posts.update in our controller. Lastly is the Index.blade.php

resources/views/Admin/posts/index.blade.php

<x-app-layout>
<x-slot name="header">

</x-slot> <br>

<div class="container">
<div class="row">
<div class="col-md-12">
@if(session()->has('message'))
<div x-data="{ show: true }" x-init="setTimeout(() => show = false, 5000)" x-show="show" class="bg-red-500 text-white py-2 px-4 rounded">
{{ session('message') }}
</div>
@endif
<div class="panel panel-default">
<div class="panel-heading">
<div class="row">
<div class="col-md-10">
Posts
</div>
<div class="col-md-2">
<a href="{{ route('admin.posts.create') }}" class="btn btn-success btn-sm">Create New Post</a>
</div>
</div>
</div>

<div class="panel-body">
<table class="table">
<thead>
<tr>
<th>Title</th>
<th>Created At</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
@foreach($posts as $post)
<tr>
<td>{{ $post->title }}</td>
<td>{{ $post->created_at }}</td>
<td>
<a href="{{ route('admin.posts.edit', $post->id) }}" class="btn btn-primary btn-sm">Edit</a>
<form action="{{ route('admin.posts.destroy', $post->id) }}" method="post" style="display: inline-block">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-danger btn-sm">Delete</button>
</form>
</td>

</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</x-app-layout>

This displays all the posts from our database. It uses the for loop to loop through all the posts and return them to our browser. Now since we need to be authenticated before accessing anything on the dashboard we will now install Laravel Jetstream for this.

5. Middleware

Middleware provides a convenient mechanism for inspecting and filtering HTTP requests entering your application.

a). An example of how to use middleware in Laravel:

php artisan make:middleware MiddlewareName

This will create a new middleware file in your app/Http/Middleware directory.

b). Open the middleware file and add your custom logic to it. For example, let’s say you want to restrict access to certain routes to only authenticated users. You can add the following code to your middleware:

public function handle(Request $request, Closure $next)
{
if (!Auth::check()) {
return redirect()->route('login');
}

return $next($request);
}

This code checks if the user is authenticated and redirects them to the login page if they’re not.

c). Register your middleware in the $routeMiddleware array in app/Http/Kernel.php:

protected $routeMiddleware = [
// ...
'middlewareName' => \App\Http\Middleware\MiddlewareName::class,
];

Replace middlewareName with the name of your middleware.

4. Apply your middleware to the routes you want to protect. You can do this by adding the middleware name to the middleware array in your route definition. For example:

Route::get('/dashboard', [DashboardController::class, 'index'])->middleware('middlewareName');

To implement authentication using Jetstream in our Laravel blog application, you can follow these steps:

a).

composer require laravel/jetstream
php artisan jetstream:install livewire

b). Migrate the database:

php artisan migrate

c). Publish Jetstream’s resources:

php artisan vendor:publish --tag=jetstream-views
php artisan vendor:publish --tag=jetstream-config
php artisan vendor:publish --tag=jetstream-migrations

d). Install NPM dependencies and compile assets:

npm install && npm run dev

e). Register the Jetstream service provider in the config/app.php file:

Laravel\Jetstream\JetstreamServiceProvider::class,

f). Generate a new User model and a migration:

php artisan jetstream:install livewire --teams
php artisan migrate

g). Update the User model to include the HasProfilePhoto and TwoFactorAuthenticatable traits:

use Laravel\Jetstream\HasProfilePhoto;
use Laravel\Jetstream\HasTeams;
use Laravel\Jetstream\TwoFactorAuthenticatable;

class User extends Authenticatable
{
use HasApiTokens;
use HasProfilePhoto;
use HasTeams;
use TwoFactorAuthenticatable;
}

8. Update the routes/web.php file to include the Jetstream routes:

Route::middleware(['auth:sanctum', 'verified'])->group(function () {
Route::get('/dashboard', function () {
return view('dashboard');
})->name('dashboard');
});

To access Jetstream from the browser, you can navigate to the URL where your Laravel application is hosted, and then append /login or /register to the URL depending on whether you want to log in or register a new user.

There is a dashboard that comes by default from Jetstream. We will not use that one instead will copy some of the code from it and update it to our index.blade.php. When you observe the index.blade.php on the top it uses the Jestream header only (I already posted check above). Next, Let’s update the Route service provider to redirect us to our file i.e index.blade.php i.e ‘resources/views/Admin/posts/index.blade.php’. So it’s something like this…

class RouteServiceProvider extends ServiceProvider
{
/**
* The path to the "home" route for your application.
*
* Typically, users are redirected here after authentication.
*
* @var string
*/
public const HOME = '/admin/posts';

/**
* Define your route model bindings, pattern filters, and other route configuration.
*/

Now it will redirect us to ‘resources/views/Admin/posts/index.blade.php’ instead of the normal dashboard.blade.php. You can delete the dashboard.blade.php if you want.

To apply middleware to our routes for admin we simply add the middleware auth like this…

// Backend
Route::middleware(['auth'])->group(function () {
Route::get('/admin/posts', 'App\Http\Controllers\Admin\PostController@index')->name('admin.posts.index');
Route::get('/admin/posts/create', 'App\Http\Controllers\Admin\PostController@create')->name('admin.posts.create');
Route::post('/admin/posts/store', 'App\Http\Controllers\Admin\PostController@store')->name('admin.posts.store');
Route::get('/admin/posts/{post}/edit', 'App\Http\Controllers\Admin\PostController@edit')->name('admin.posts.edit');
Route::patch('/admin/posts/{post}', 'App\Http\Controllers\Admin\PostController@update')->name('admin.posts.update');
Route::delete('/admin/posts/{post}', 'App\Http\Controllers\Admin\PostController@destroy')->name('admin.posts.destroy');
});

Now no one can access these routes without being authenticated. This is called group middleware in Laravel. We can proceed to the front end and implement a similar thing. You already have the routes for the front end in web.php.

Frontend Views.

On the resources/views create a folder posts resources/views/posts and then create these two files index.blade.php and show.blade.php.

resources/views/posts/index.blade.php

<x-front-layout>
<div class="container">
<h1 class="text-center mb-5">Blog Posts</h1>
<!-- place urls generation here... -->
<div class="row justify-content-center">
@foreach($posts as $post)
<div class="col-md-6 mb-3">
<div class="card h-100">
<img src="{{ asset('storage/' . $post->image) }}" class="card-img-top" alt="{{ $post->title }}">
<div class="card-body">
<h2 class="card-title"><a href="{{ route('posts.show', $post->id) }}">{{ $post->title }}</a></h2>
<!-- <h2 class="card-title"><a href="{{ url('posts', $post->id) }}">{{ $post->title }}</a></h2> URL For a specific blog post -->
<p class="card-text">{{ Str::limit($post->body, 150) }}</p>
</div>
<div class="card-footer">
<small class="text-muted">{{ $post->created_at->format('M d, Y') }}</small>
</div>
</div>
</div>
@endforeach
</div>
</div>
</x-front-layout>

A simple form for displaying our posts coming from the database. For the x-front-layout, we will create it this way. Go to a folder called “components” and create it there i.e “front-layout.blade.php”. The reason we are doing this is to disable the scripts that come from installing Laravel Jetstream with Livewire and use ours. So this is what we have…

front-layout.blade.php

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">

<title>{{ config('app.name', 'My Blog') }}</title>

<!-- Fonts -->
<link rel="preconnect" href="https://fonts.bunny.net">
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" />
<!-- styles -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css" integrity="sha384-TX8t27EcRE3e/ihU7zmQxVncDAy5uIKz4rEkgIXeMed4M0jlfIDPvg6uqKI2xXr2" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="{{ asset('css/styles.css') }}"> 

<!-- Scripts -->
<!-- @vite(['resources/css/app.css', 'resources/js/app.js']) -->
</head>

<body>
<main>
@include('navbar')
{{ $slot }}
</main>

<footer class="bg-dark text-white py-3">
<div class="container text-center">
<p>© {{ date('Y') }} WebDev. All Rights Reserved.</p>
</div>
</footer>

</body>
</html>

Let’s create the navbar…

navbar.blade.php

<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="{{ url('/') }}">WebDev</a> <!--URL For the blog index page -->
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item active">
<a class="nav-link" href="#">Home <span class="sr-only">(current)</span></a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Blog</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Contact</a>
</li>
</ul>
</div>
</nav>

The other file is show.blade.php

resources/views/posts/show.blade.php

<x-front-layout>
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card mb-3">
<div class="card-body">
<h1 class="card-title text-center">{{ $post->title }}</h1>
<hr>
<img src="{{ asset('storage/' . $post->image) }}" class="card-img-top" alt="{{ $post->title }}">


<p class="card-text">{{ $post->body }}</p>
<hr>
<p class="card-text"><small class="text-muted">{{ $post->created_at->format('M d, Y') }}</small></p>
</div>
</div>

<div class="card mb-3">
<div class="card-body">
<h2 class="card-title">Comments</h2>
<hr>

@foreach($post->comments as $comment)
<div class="card mb-3">
<div class="card-body">
<p class="card-text">{{ $comment->body }}</p>
<hr>
<p class="card-text"><small class="text-muted">{{ $comment->created_at->format('M d, Y') }}</small></p>
</div>
</div>
@endforeach

<form action="{{ route('comments.store', $post) }}" method="POST" class="mt-5">
@csrf
<div class="form-group">
<label for="body">Add Comment:</label>
<textarea name="body" id="body" rows="5" class="form-control"></textarea>
</div>
<input type="hidden" name="post_id" value="{{ $post->id }}">
<button type="submit" class="btn btn-primary mt-3">Submit</button>
</form>
</div>
</div>
</div>
</div>
</div>
</x-front-layout>

This form returns a single post. It has a comment box let me explain…

Create a controller known as CommentController.php and update it as follows…

CommentController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Comment;

class CommentController extends Controller
{
public function store(Request $request, $postId)
{
$validatedData = $request->validate([
'body' => 'required',
]);

$comment = new Comment();
$comment->body = $validatedData['body'];
$comment->post_id = $postId;
$comment->save();

return redirect('/posts/' . $postId)->with('success', 'Comment created successfully!');
}
}

This is responsible for handling all our comments for the blogging system. And let’s have the other controller “PostController.php”

PostController.php

<?php

namespace App\Http\Controllers;

use App\Models\Post;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;

class PostController extends Controller
{

public function index(Post $post)
{
   $posts = Post::orderBy('created_at', 'desc')->paginate(2);
   return view('posts.index', compact('posts'));
}

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

This controller handles the frontend side i.e displaying the posts.

Models

In Laravel 10, a model is a PHP class that represents a single database table. It provides an object-oriented way to interact with the database, allowing you to easily retrieve, insert, update, and delete records in the table.

A model typically includes the database connection configuration, table name, and relationships with other models. It also includes methods for querying
the database, such as retrieving records based on certain criteria, sorting records, and grouping records. Remember to create the models. Comment.php, Post.php, and User.php (User.php comes by default no need to create it)

Comment.php

<?php

namespace App\Models;

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

class Comment extends Model
{
use HasFactory;

protected $fillable = [
'body',
'post_id',
];

public function post()
{
return $this->belongsTo(Post::class);
}
}

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',
'body',
'user_id',
];

public function user()
{
return $this->belongsTo(User::class);
}

public function comments()
{
return $this->hasMany(Comment::class);
}
public function setImage($image)
{
$imagePath = $image->store('public/images');
$this->image_path = str_replace('public/', 'storage/', $imagePath);
}

}

6. Asset Bundling

Here we look at Laravel Mix. However, Vite has replaced Laravel Mix in new Laravel installations. How does Laravel Mix work?

For example, I have To bundle custom.css and styles.css in the public directory using Laravel Mix, you can modify the code in the webpack.mix.js file as follows:

a) Install Laravel Mix using npm:

npm init -y

npm install laravel-mix --save-dev

b) Secondly create webpack.mix.js by running

touch webpack.mix.js.

Paste this code to the file.

let mix = require('laravel-mix');

mix.styles(['public/css/custom.css', 'public/css/styles.css'], 'public/css/all.css');

This code will bundle the two CSS files and output a new file named all.css in the public/css directory.

c) You can then include this file in your view using

<link> tag. <link rel="stylesheet" href="{{ asset('css/all.css') }}">

d) Run the command

npx mix

in your terminal to compile the assets.

7. Laravel Responses

All routes and controllers should return a response to be sent back to the user’s browser. Laravel provides several different ways to return responses.

In your PostController, let’s say you want to return a JSON response of a single post:

public function show(Post $post)
{
  return response()->json([
  'id' => $post->id,
  'title' => $post->title,
  'body' => $post->body,
  'created_at' => $post->created_at,
  'updated_at' => $post->updated_at,
]);
}

In the admin PostController.php

public function index()
{
$posts = Post::all();
// return response()->json(['posts' => $posts]); // a JSON response for a list of posts
return view('admin.posts.index', compact('posts'));
}

You can use responses in different ways in your blog application, such as:

Returning JSON response: You can use Laravel’s json method to return a JSON response for an API endpoint, such as an endpoint to fetch posts or comments.

public function index()
{
$posts = Post::all();
return response()->json(['posts' => $posts]); // a JSON response for a list of posts
}

Returning file download response: You can use the download method to return a file download response, such as a PDF or image file.

public function download()
{
$file = public_path()."/path/to/file.pdf";
$headers = array(
'Content-Type: application/pdf',
);
return response()->download($file, 'filename.pdf', $headers);
}

Returning view response: You can use the view method to return a view response, such as a view to display a post or a list of posts.

public function index(Post $post)
{
$posts = Post::orderBy('created_at', 'desc')->paginate(2);
return view('posts.index', compact('posts'));
}

Redirecting response: You can use the redirect method to redirect the user to another page, such as after submitting a form or logging in.

session()->flash('message', 'Your post has been created!');
return redirect()->route('admin.posts.index');

8. URL Generation

Laravel provides several helpers to assist you in generating URLs for your application.

public function index(Post $post)
{
// // Laravel URL Generation
// $posts = Post::orderBy('created_at', 'desc')->paginate(2);

// // Generate a URL using the url() helper function
// $url = url('/post');

// // Generate a URL using the route() helper function
// $routeUrl = route('posts.index');

// // Generate a URL for a named route with a parameter
// $post = Post::first();
// $postUrl = url('posts', $post->id);

// // Generate a URL with a secure HTTPS scheme
// $secureUrl = URL::secure('/post');

// // Generate a URL with a trailing slash
// $trailingSlashUrl = URL::to('/post/') . '/';

// return view('posts.index', compact('posts', 'url', 'routeUrl', 'postUrl', 'secureUrl', 'trailingSlashUrl'));

}

You can inspect these URLs in your views like this…

index.blade.php

You can change this

'<h2 class="card-title"><a href="{{ route('posts.show', $post->id) }}">{{ $post->title }}</a></h2>'

to this

'<h2 class="card-title"><a href="{{ url('posts', $post->id) }}">{{ $post->title }}</a></h2>'

URL For a specific blog post

The rest…

<!-- Generating a URL using the url() helper function -->
<a href="{{ url('/blog') }}">Blog Home</a>

<!-- Generating a URL using the route() helper function -->
<a href="{{ route('blog.index') }}">Blog Home</a>

<!-- Generating a URL for a named route with a parameter -->
@foreach($posts as $post)
<a href="{{ route('blog.show', $post->slug) }}">{{ $post->title }}</a>
@endforeach

<!-- Generating a URL with a secure HTTPS scheme -->
<a href="{{ URL::secure('/blog') }}">Blog Home (Secure)</a>

<!-- Generating a URL with a trailing slash -->
<a href="{{ URL::to('/blog/') . '/' }}">Blog Home (Trailing Slash)</a>

To inspect the generated URLs in Laravel, you can simply visit them in your web browser or output them in your view files using the {{ }} syntax.

For example, you can add the following code snippet in your view file to output the $url and $secureUrl variables:

<p>URL: {{ $url }}</p>

<p>Secure URL: {{ $secureUrl }}</p>

9. Session

In Laravel 10, you can use sessions to store data that persists across multiple requests. This can be useful for a real-time blog, where you might want to store user preferences or other data that should be available through the user’s session. Here’s an example of how you can use sessions in a Laravel 10
blog application.

For example, we can use sessions to flash messages to the user once data is recorded in our database.

Then, in your view, you can check for the presence of the success key in the session and display the message accordingly:

@if(session('success'))
<div class="alert alert-success">
{{ session('success') }}
</div>
@endif

then in your controller, you have something like this…

 // Store a flash message in the session
session()->flash('success', 'Your post has been created!');
return redirect()->route('admin.posts.index');

Remove Flash Messages Using Alpine Js

You can remove the flash messages using Alpine Js as follows…

Place this code in your view that returns a flash message. For example

 @if(session()->has('message'))
     <div x-data="{ show: true }" x-init="setTimeout(() => show = false, 5000)" x-show="show" class="bg-red-500 text-white py-2 px-4 rounded">
          {{ session('message') }}
     </div>
 @endif

and in your controller you can have a code like this

 // Store a flash message in the session
        session()->flash('message', 'Your post has been created!');
        return redirect()->route('admin.posts.index');

I have posted the controller for admin and the view which have all of these. This means you can use this session across multiple views by just placing the above code.

Changing Theme Mode To Light or Dark Theme Using Session

Another form of the session is the changing theme on our website. Let’s see this in our blog.

First, make sure sessions are configured in your application. In the .env file, set the SESSION_DRIVER to your preferred session storage, e.g., file, database, Redis, etc.

SESSION_DRIVER=file

Next, let’s create a basic blog layout. In your routes/web.php file, add the following routes:

// Theme
Route::get('/set-theme/{theme}', 'App\Http\Controllers\PostController@setTheme')->name('set.theme');

Now, let’s update our controller with the following content:

public function setTheme($theme)
{
// Set the theme in the session
session(['theme' => $theme]);

return redirect()->route('posts.index');
}

Next, Let’s update the front-layout.blade.php file with the following content:

@if (session('theme') === 'dark')
<link rel="stylesheet" href="{{ asset('css/dark-theme.css') }}" id="theme-link">
@else
<link rel="stylesheet" href="{{ asset('css/light-theme.css') }}" id="theme-link">
@endif

Then in our navbar.blade.php

<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="{{ url('/')}}">WebDev</a> <!--URL For the blog index page -->
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item active">
<a class="nav-link" href="#">Home <span class="sr-only">(current)</span></a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Blog</a>
</li>
<!-- Add your navigation here -->
<li class="nav-item">
<button id="theme-toggle" class="nav-link">Switch Theme</button>
</li>
<!-- <li class="nav-item">
<a class="nav-link" href="{{ route('set.theme', 'light') }}">Light Theme</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ route('set.theme', 'dark') }}">Dark Theme</a>
</li> -->
<li class="nav-item">
<a class="nav-link" href="#">Contact</a>
</li>
</ul>
</div>
</nav>

<script>
const themeToggle = document.querySelector('#theme-toggle');
const themeLink = document.querySelector('#theme-link');

themeToggle.addEventListener('click', function() {
if (themeLink.href.endsWith('light-theme.css')) {
themeLink.href = "{{ asset('css/dark-theme.css') }}";
document.cookie = "theme=dark";
} else {
themeLink.href = "{{ asset('css/light-theme.css') }}";
document.cookie = "theme=light";
}
});
</script>

Lastly, Create the corresponding theme CSS files public/css/light-theme.css and public/css/dark-theme.css.

public/css/light-theme.css

/* Light-Theme */

body {
background-color: #f5f5f5;
color: #333;
}

.panel {
background-color: #fff;
border-color: #ddd;
color: #333;
}

.panel-heading {
background-color: #f5f5f5;
border-bottom-color: #ddd;
color: #333;
}

.btn-primary {
background-color: #007bff;
border-color: #007bff;
color: #fff;
}

.btn-primary:hover,
.btn-primary:focus {
background-color: #0069d9;
border-color: #0062cc;
color: #fff;
}
public/css/dark-theme.css

/* Dark-Theme */
body {
background-color: #292b2c;
color: #fff; /* Set color of body text */
}

.panel {
background-color: #424242;
border-color: #333;
color: #f5f5f5;
}

.panel-heading {
background-color: #333;
border-bottom-color: #333;
color: #f5f5f5;
}

.btn-primary {
background-color: #007bff;
border-color: #007bff;
color: #fff;
}

.btn-primary:hover,
.btn-primary:focus {
background-color: #0069d9;
border-color: #0062cc;
color: #fff;
}

.card-text {
color: #000!important;
}

.card-title {
color: #000!important;
}

You can now switch themes in your laravel 10 blogs.

8. Error Handling

To implement error handling in your Laravel blog, you can follow these steps:

a) Create custom error pages

First, you’ll want to create custom error pages for common HTTP error codes like 404, 500, etc. In your resources/views/errors directory, create Blade templates for each error code you want to handle, e.g., 404.blade.php, 500.blade.php, etc. Design these pages to display a user-friendly error message.

For example, in resources/views/errors/404.blade.php:

<x-front-layout>

@section('title', 'Page Not Found')

<div class="container my-5">
<div class="jumbotron text-center">
<h1 class="display-4">404 - Page Not Found</h1>
<p class="lead">We're sorry, but the page you're looking for doesn't exist.</p>
<hr class="my-4">
<a href="/" class="btn btn-primary btn-lg">Return to Home</a>
</div>
</div>
<br><br><br>

</x-front-layout>

b) Handle exceptions

In the app/Exceptions/Handler.php file, you can define how your application should handle different types of exceptions.

Inside the report method, you can log exceptions or send them to an external service. By default, the base class’s report method would do this for you.

public function report(Throwable $exception)
{
parent::report($exception);
}

Inside the render method, you can customize the response sent to the user upon encountering an exception. You can check for specific exception types and return a custom response or view.

public function render($request, Throwable $exception)
{
   if ($exception instanceof NotFoundHttpException) {
   return response()->view('errors.404', [], 404);
}

// Add more exception handling cases here, if needed

    return parent::render($request, $exception);
}
}

c) Validate user input

To handle validation errors, you can use Laravel’s built-in validation in your controllers. When validation fails, Laravel will automatically redirect the user back to the previous page, displaying the validation errors.

In your Blade templates, you can display validation error messages. For example, in your create.blade.php file:

@if ($errors->any())
<div class="alert alert-danger">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif

By following these steps, you will have a basic error-handling mechanism in place for your Laravel blog. You can further customize and extend these methods to handle any specific exceptions or error cases that your application may encounter.

9. Logging

In Laravel 10, logging is handled using the Monolog library, which provides support for various log handlers. Laravel makes it easy to configure logging into your application through the config/logging.php configuration file.

Here’s an example of how you might use logging into a Laravel 10 blog application:

Configure logging channels in config/logging.php:

return [
'default' => env('LOG_CHANNEL', 'stack'),

'channels' => [
'stack' => [
'driver' => 'stack',
'channels' => ['single'],
],

'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
],

// Add additional channels as needed.
],
];

In this configuration, the default logging channel is ‘stack’, which in turn uses the ‘single’ channel. The single channel logs messages to a file named laravel.log in the storage/logs directory at a log level specified by the LOG_LEVEL environment variable or ‘debug’ by default.

Use the logger in your blog application:

In your blog application, you can use the Log facade to log messages with various log levels. Here’s an example of how you might use logging in a PostController: in the frontend side

public function index(Post $post)
{
try {
$posts = Post::paginate(2);
return view('posts.index', compact('posts'));
} catch (\Exception $e) {
Log::error("PostController@index: Error while fetching posts: " . $e->getMessage());
return view('posts.index')->withErrors(['message' => 'Error fetching posts.']);
}
return view('posts.index', compact('posts'));
}
public function show(Post $post)
{
try {
$post = Post::where('id', $id)->firstOrFail();
return view('posts.show', compact('post'));
} catch (\Exception $e) {
Log::error("PostController@show: Error while fetching post: " . $e->getMessage());
return redirect('/')->withErrors(['message' => 'Error fetching post.']);
}
}
}

In this example, the index and show methods of the PostController use the Log::error() method to log error messages if an exception is thrown while fetching posts.

That’s it! Now you have a basic understanding of Laravel 10 basics by creating a fully functional blogging system.

Please share this tutorial with others on social media and elsewhere thanks! Comment below if you have any questions.

Sign up for free tutorials in your inbox.

We don’t spam! Read our privacy policy for more info.

Leave a Comment

Your email address will not be published. Required fields are marked *