You're warmly welcome to the second part of Creating Your First Blog With TALL (Tailwind CSS, Alpinejs, Laravel, Livewire) tutorial series. In the previous episode, we delved into some basic introduction to the TALL stack and set our development machine up for building websites with the TALL stack. We also looked at what each item in the stack does. For the benefit of doubt, you can always read part one of this tutorial series here if you haven't done so.

For this part of the tutorial series, you'll build upon the first part by configuring Laravel Jetstream and Fortify, database connection, routes, creating a symlink to the public folder to make file uploads possible and changing the default application logo.

At the end of this tutorial, you'll learn:

  • How to configure Jetstream and Fortify to enable profile photo uploads and email verification respectively.
  • How to configure a freshly installed Laravel database connection using the .env file.
  • How to create routes in a Laravel application.
  • How to create a symlink to the public folder for file uploads.
  • How to change the default Laravel Jetstream app logo to your own logo.

In the previous edition, I indicated we're going to create models and migrations but it seems this episode has gotten too long and adding that will make it worse, so I decided to send that part to the next session. OK, let's get going. :)

Configuring Jetstream and Fortify

Configuring Jetstream

When you ran php artisan jetstream:install in the first part of this tutorial series, Jetstream configuration files were published to your project's config folder. These include config files for both Jetstream (named jetstream.php) and Laravel Fortify (named fortify.php). The jetstream.php file is what we're going to configure in this section.

If you had gone to your profile page in the Jetstream dashboard after logging in, you'd notice that there is no field provided for you to upload your profile photo. The only fields you see are your name and email address:

No profile photo upload option

No profile photo upload option

Though uploading profile pictures is not available in the dashboard, Jetstream provides support for it out of the box and we're going to enable that feature.

To enable profile photo upload, while in the project root, go to the config folder and open jetstream.php. Scroll down until you find the features array. Uncomment Features::profilePhotos() so that the features array looks like this:

// tall-blog/config/jetstream.php    
/*
    |--------------------------------------------------------------------------
    | Features
    |--------------------------------------------------------------------------
    |
    | Some of Jetstream's features are optional. You may disable the features
    | by removing them from this array. You're free to only remove some of
    | these features or you can even remove all of these if you need to.
    |
    */

    'features' => [
        // Features::termsAndPrivacyPolicy(),
        Features::profilePhotos(),
        // Features::api(),
        // Features::teams(['invitations' => true]),
        Features::accountDeletion(),
    ],

Save the file and refresh your browser. You should now see a Photo field with a SELECT A NEW PHOTO button you can click to choose a picture:

Photo field now available

Photo field now available

As with all Laravel configuration files, the features array has a comment block that explains what this line in the configuration does. Uncommenting a feature from this array enables it for your project and removing it or commenting it out does the reverse.

Configuring Fortify

While Jetstream deals with the user interface part of our scaffold, Fortify handles the authentication part. Thus, it's now time for us to enable email verification for all users so that users won't just provide invalid email addresses to register for our blog.

This is also done in a features array, just like in Jetstream, but in a different configuration file - fortify.php. Open the fortify.php configuration file in the config folder and ensure the Features::emailVerification() line is uncommented:

// tall-blog/config/fortify.php 
/*
    |--------------------------------------------------------------------------
    | Features
    |--------------------------------------------------------------------------
    |
    | Some of the Fortify features are optional. You may disable the features
    | by removing them from this array. You're free to only remove some of
    | these features or you can even remove all of these if you need to.
    |
    */

    'features' => [
        Features::registration(),
        Features::resetPasswords(),
        Features::emailVerification(),
        Features::updateProfileInformation(),
        Features::updatePasswords(),
        Features::twoFactorAuthentication([
            'confirmPassword' => true,
        ]),
    ],

Save the file and refresh your browser. Now every user who have registered on your blog will have to verify their email address for their account to be complete.

Configuring the .env File

Like many modern frameworks, Laravel keeps some common and secret configuration information in a .env file, also known as an environment file. These information include database connection details, cache driver details, mail configuration information, API keys and other app-level configuration details such as the app name, url and app secret key. All these, plus more, are housed in the .env file, which by default, is kept in the root of your project.

Note: The .env file may not be visible if you don't enable showing of hidden files on your computer. If that's the case, follow the documentation on how to enable display of hidden files for your operating system.

Specifically, what we want to do is change the default database connection and app-level details to the desired values so our blog can function properly. We do this by changing the values in the APP_ and DB_ prefix keys. These, in addition to the LOG_ prefix keys, are mostly located in the top section of the .env file. Change those DB_ and APP_ prefixed keys so they look the same as below:

# tall-blog/.env

APP_NAME="Tall Blog"
APP_ENV=local
APP_KEY=base64:wobqK/lLWqFYTNkUDRhtr5TDPqSyNC1p4YaV5CTFIR4=
APP_DEBUG=true
APP_URL=127.0.0.1:8000

LOG_CHANNEL=stack
LOG_LEVEL=debug

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=tall_blog
DB_USERNAME=root
DB_PASSWORD=root1234
...
...
...

Save the file and let's look at what each line means:

  1. Line 1 is the app name. This is where you specify the name you want to give your blog.
  2. Line 2 is the environment you're running. Here you specified that your app is running in the local environment. You may set this to other values such as staging, production, etc as and when you need to do so.
  3. Line 3 sets the APP_KEY to a random 32-bit character sring used for encrypting and securing sessions, tokens and other user level encryption.
  4. Line 4 defines whether debugging is enabled (true, in this case) or not. Line 5 sets the app url.
  5. The LOG_ keys on lines 7 and 8 are used for error logging configuration information. We specify the channel as 'stack'(which aggregates multiple log channels into a single channel) and the logging level as debug(verbose) since we're running in development environment.
  6. Lines 10-15 are our database configs and are mostly self-explanatory:
  7. DB_CONNECTION is the database connection name.
  8. DB_HOST is the database host we'll running our database server on.
  9. DB_PORT is the port the database host server runs on.
  10. DB_DATABASE is the name our database we'll be connecting to in our blog.
  11. DB_USERNAME and DB_PASSWORD are the username and password, respectively, we'll use in connecting to our database.

To be able to follow along, you'll need to have a database server, preferably MySQL server, running on your computer with the tall_blog database and user with username root and password root1234 created. You can get a ready-made MySQL server bundled with other servers in a stack such as XAMPP, LAMP, MAMP or WAMP. These stacks are very easy to setup and use, and fulfills all requirements of the Laravel framework.

If you'd rather want to have everything Laravel requires and more, which won't mess up with your system, you can try either Laravel Homestead or Laravel Valet (for Mac). Homestead is a Vagrant box that incorporates all your server software on top of the Ubuntu distro in a Vagrant provisioned Virtual Machine. It runs on Linux, Windows and Mac systems. Valet, on the other hand, runs only on Mac and is even simpler to setup, very fast and very lightweight. So you may consider Valet if you're on Mac. The other obvious advantage of using Homestead or Valet is that you get a development environment that is similar to your production server. So you'd expect lesser hassles during deployment.

Adding Routes to Our Application

One of the benefits of modern frameworks is their routing capabilities and Laravel is not an exception. Routing in Laravel allows you to intercept HTTP requests and redirect those requests to the appropriate actionable closure or controller for handling.

Routing in any web application is as important as designing good user interfaces and putting everything in place to have a professional looking website. Frameworks with elegant routing systems take the pain of manually linking to hundreds and thousands of pages out of developers and simplify this process into pattern-based, definitive routes - making handling multitudes of similar urls a breeze.

In Laravel, all routes definitions are held in the routes folder. Web routes(user facing webpage routes) are defined in a file named web.php and this is where you'd spend most of your time when defining routes for your web application. When we installed Laravel, a default route for our homepage was already defined for us, that's why we're able to test out our installation.

Since we have decided to create our own routes, open and remove everything in the web.php file and replace the file's contents with the following:

// tall-blog/routes/web.php
<?php

use Illuminate\Support\Facades\Route;
/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/', function () {
    return view('welcome');
});

Route::get('categories/{category}', function ($category) {
    return view('welcome');
});

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

We started by making use of the Illuminate\Support\Facades\Route facade. This is all we need from Laravel to define our routes. The Route facade contains a lot of useful methods for defining our routes, popular among them are methods representing various popular HTTP request methods such as GET, POST, PUT, and DELETE. Methods that have no representation in the Routes facade but are in the HTTP specification can be specified by using the Route::any() method.

All our routes are defined with the Route::get() method, corresponding to the HTTP GET method. The Route::get() method makes a GET request to the given URI argument to be processed by the provided action argument. The action argument can be a closure, a controller or any PHP object that returns a value. For now, we decided to use closures because we haven't created any controller yet.

In the first route definition, we're routing to the homepage of our blog, for which we returned a view from a closure. The view helper function looks for the given view in the resources/views folder, parses it and returns its contents as HTML.

Our second route does the same thing, only that it routes to a given category. The {category} part in this URI is an example of a route parameter. Route parameters are passed to the action closure or controller and are available to them. An example request to this route will be http://127.0.0.1:8000/categories/web-design. In this example, the 'web-design' part will be made available to our closure.

Our third part of the routes is a bit complex. We're using route groups here. Defining routes this way let you group all your routes that have a common attribute ('prefix' and 'middleware' here) into an organized, single block of routes that are easier to trace and maintain. Thus, all the routes in this route group are for the dashboard part of our application - so they have the common prefix 'dashboard' before them.

We've also applied the auth:sanctum middleware to this route group. Meaning, all requests to any of the routes within this group will have to be authenticated in order to work.

Our first route in the group is the dashboard page itself. We decided to make it a named route. Naming routes makes it easier for you to refer to them in any part of your code without having to concatenate strings together. Currently, it returns the default dashboard view, as all other routes in the group. The last two routes are for adding posts and categories respectively(whose views we haven't implemented yet).

Creating A Symlink

Our next task is to create a symbolic link(symlink) from the storage/app/public folder to the public folder. This makes uploads in the storage/app/public folder available in the public folder(technically, this is not true since symlinks only point to the actual files and are not a copy of the actual files). To create the symlink, enter this command in your favorite terminal while in your project root:

php artisan storage:link

This should create the storage symlink in your public folder:

Storage symlink

Storage symlink

Changing the App Logo

If you had noticed when you were to login/register, the default Jetstream logo was displayed on both the registration and login pages. In deed, it's also utilized in the app dashboard.

If you're serious about being professional in your work, you wouldn't want to be showing this logo to your users. Jetstream doesn't stand in your way, either, to customize this logo to your liking. To be able to customize the logo, though, we have to publish Jetstream views. Enter this to do so:

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

After running the above command, you should now see a new resources/views/vendor/jetstream folder with sub-folders housing various blade template views. All these blade views in the components sub-folder are Laravel components (learn more about components here). Our interest, for now, is on the files resources/views/vendor/jetstream/components/application-logo.blade.php, resources/views/vendor/jetstream/components/authentication-card-logo.blade.php, and resources/views/vendor/jetstream/components/application-mark.blade.php since they hold the default Jetstream logos.

First, open and delete everything in resources/views/vendor/jetstream/components/application-logo.blade.php, make sure it contains only the following:

// tall-blog/resources/views/vendor/jetstream/components/application-logo.blade.php
<img {{ $attributes }} src="{{ asset('logo.png') }}"/>

Save the file. Here, we're just linking to an image named logo.png(can be downloaded here) using the HTML img tag in the public folder. You can see that we used the Laravel asset() helper function (which, of course loads assets) to link to the image. The {{ and }} blade template tags basically act like the PHP echo statement. $attributes is a placeholder for a list of HTML attributes we may like to specify when we're to use this component.

Now open resources/views/vendor/jetstream/components/authentication-card-logo.blade.php and ensure its contents match below:

// tall-blog/resources/views/vendor/jetstream/components/authentication-card-logo.blade.php
<a href="/">
    <x-jet-application-logo class="w-24 h-24 rounded-full"/>
</a>

What we do here is making use of our application logo. We wouldn't want the logo to be oversized, so we use Tailwind CSS' width and height utilities to resize it and make it a circular shape. Since all the views in the resources/views/vendor/jetstream/components folder are Laravel components in the Jetstream namespace, we display the logo using a blade component tag here with the <x-jet-[component-name]/> syntax. Otherwise, component tags start with the string x- followed by the kebab case name of the component.

Let's now turn our attention to the last file we need to change - resources/views/vendor/jetstream/components/application-mark.blade.php. This file holds the logo displayed in the dashboard our application. Please open it and replace its content with:

// tall-blog/resources/views/vendor/jetstream/components/application-mark.blade.php
<x-jet-application-logo class="w-20 h-20"/>

This is the same as we did for the authentication card, the only difference is that we've reduced the size of the logo and didn't add the circular shape.

Now if you visit the login and registration pages you'll see the change:

Login page with brand logo

Login page with brand logo

Registration page with branded logo

Registration page with branded logo


This is the dashboard page:

Dashboard with branded logo

Dashboard with branded logo



Congratulations on the journey so far. It's been a rough one and I am happy you read to this end. In our next episode, you're going to learn how to create models and migrations. You'll also learn how to run these migrations in order to create the tables we'll need for this blog. Thank you and see you for the next episode.