TALL stack

Creating Your First Blog With TALL – Part Two

Table of Content

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 the 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:

  1. How to configure Jetstream and Fortify to enable profile photo uploads and email verification respectively.
  2. How to configure a freshly installed Laravel database connection using the .env file.
  3. How to create routes in a Laravel application.
  4. How to create a symlink to the public folder for file uploads.
  5. 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

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
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::api(),
        // Features::teams(['invitations' => true]),

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

As with all lines in 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
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' => [
            'confirmPassword' => true,

Save the file and refresh your browser. Now every user who has 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. This information includes 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 at the root of your project.

Note: The .env file may not be visible if you don't enable the showing of hidden files on your computer. If that's the case, follow the documentation on how to enable the 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"



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

  • Line 1 is the app name. This is where you specify the name you want to give your blog.
  • 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.
  • Line 3 sets the APP_KEY to a random 32-bit character string used for encrypting and securing sessions, tokens, and other user-level encryption.
  • Line 4 defines whether debugging is enabled (true, in this case) or not while Line 5 sets the app URL.
  • 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 a development environment.
  • Lines 10-15 are our database configs and are mostly self-explanatory:
    1. DB_CONNECTION is the database connection name.
    2. DB_HOST is the database host we'll running our database server on.
    3. DB_PORT is the port the database host server runs on.
    4. DB_DATABASE is the name our database we'll be connecting to in our blog.
    5. 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 a MySQL server, running on your computer with the
database and user with username
and password
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 set up and use and fulfill 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 set up, 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 route 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

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');

    Route::get('post/add', function () {
        return view('dashboard');

    Route::get('category/add', function () {
        return view('dashboard');

We started by making use of the
facade. This is all we need from Laravel to define our routes. The
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
facade but are in the HTTP specification can be specified by using the

All our routes are defined with the
method, corresponding to the HTTP GET method. The
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
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
part in this URI is an example of a [route parameter](https://laravel.com/docs/8.x/routing# route-parameters). Route parameters are passed to the action closure or controller and are available to them. An example request to this route will be
. 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](https://laravel.com/docs/8.x/routing# route-groups). Defining routes this way lets 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
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](https://laravel.com/docs/8.x/routing# named-routes). Naming routes make 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

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. Indeed, 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](https://laravel.com/docs/8.x/blade# components)). 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
tag in the public folder. You can see that we used the Laravel
helper function (which, of course, loads assets) to link to the image. The
blade template tags basically act like the PHP
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"/>

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
syntax. Otherwise, component tags start with the string
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 of 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 page you'll see the change:
Login page with brand logo

And the registration page:

Registration page with branded logo

This is the dashboard page:
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.


Leave a comment

Your email address will not be published.