Web Development by Solarise

The Solarise.dev Blog

How To Build A Reactive, Responsive Blog Application With Laravel & Livewire

In this tutorial we’ll build a Laravel blog using Livewire for seriously snappy front-end responsiveness.

Blog archive Robin Metcalfe 14th September 2023

Laravel & Livewire make a powerful team

I use Laravel for a lot of my freelance web development projects. It’s an awesome framework and can be used to built a whole range of apps and sites. Livewire makes it even more useful.

Livewire is a framework for Laravel which takes the pain out of creating highly responsive & dynamic front-end UIs. Usually, you’d need to install a reactive framework like React, Vue or AlpineJS to get highly interactive front-end functionality. But with Livewire, all of that is handled for you without needing to step out from the familiar Laravel codebase.

Livewire was created by Caleb Porzio and has since gained significant traction in the Laravel community.

It lets developers write front-end components using the same syntax and techniques that they’re used to for backend development.

Especially cool is the ability to reflect the server’s state on the front-end without a full page reload. Nice! This lets us create websites that respond nearly instantly to user interaction without having to refresh the entire page.

We’ll make use of this to introduce two responsive features to our blog:

  1. Pagination, filtering through the list of articles without reloading the page
  2. Moving between the article index and an individual post without reloading the page

Setting up a simple Laravel blog with Livewire

Let’s get started – we’re going to build a very basic blog application that’ll show an index page of articles as well as a single article view.


Our simple blog application will feature:

  • Pagination
  • Blog content with featured images
  • Routing for both the articles index and individual blog pages
  • Super-fast navigation thanks to the wire:navigate directive

Introduction to Livewire

Livewire is fairly easy to set up. You’ll need to make sure you’ve got a suitable local environment to run this tutorial on your own machine.

  • If you’re developing on Windows, using Windows Subsystem for Linux (WSL) will make your life easier
  • Composer
  • Your editor of choice (VS Code is a great option)
  • No particular build environment is required – we’ll use php artisan serve to run a local dev server

Let’s get going

If you haven’t already got a Laravel project set up, navigate to where you want the project to live and run

composer create-project --prefer-dist laravel/laravel your-project-name

Composer will go away and do its thing for a while, you’ll see a bunch of stuff getting installed.

This Laravel blog tutorial sure has a lot of dependencies!

If you get any errors at this stage, you might need to ensure you’ve got the right PHP version and all appropriate extensions are installed – you may also need to install pdo_sqlite with sudo apt install php8.2-sqlite3 (swap out the appropriate PHP version)

Then navigate into your newly created project folder

cd your-project-name

Installing Livewire

With your Laravel blog project now installed, it’s time to get Livewire set up and configured

composer require livewire/livewire

We’ll also need to publish Livewire’s configuration settings

php artisan livewire:publish

This will copy Livewire’s default config settings into config/livewire.php – this isn’t vital, but it’s a nice-to-have if you want an easy way to review and update any of Livewire’s core configuration values.

Getting The Database Connected

The easiest way to get a database up and running for this simple blog app will be to make use of sqlite. This is a super-easy file-based database that’s great for local development (you can use it in production too if you really want, though I wouldn’t recommend it!)

First, make sure sqlite is installed

sudo apt-get install sqlite3

Then create a new database file. From the root of your Laravel project folder, run

touch database/database.sqlite

You’ll also need to update your project’s .env file to use this new database

DB_CONNECTION=sqlite
DB_DATABASE=/absolute/path/to/your_project_name/database/database.sqlite

Running Your New App

If all has gone to plan, you can now run

php artisan serve

and your fresh new Laravel installation will become available to view in your browser at https://127.0.0.1:8000 (you should be able to confirm the precise access URL after artisan serve has finished processing)

Setting up the Laravel blog & articles structure

For this example, let’s set up an Article model and controller, and populate the database with some example articles that we’ll use when building out our Livewire views.

Each article will represent a Laravel blog page, with a title, blog content and a featured image (we’ll use LoremFlickr to populate dummy images)

That’s all very basic for now, but Laravel will make it a breeze to expand upon all this later if you want to carry on building on the basics outlined here.

First, let’s create a new migration with the artisan command

php artisan make:migration create_articles_table

This’ll create a new database migration file under database/migrations. Modify the up() method so it looks like this:

public function up()
{
    Schema::create('articles', function (Blueprint $table) {
        $table->id(); // Auto-incremental primary key
        $table->string('title'); // Title of the article
        $table->text('content'); // Content of the article
        $table->string('image')->nullable(); // Adding the image column
        $table->timestamps(); // created_at and updated_at timestamps
    });
}

Then run

php artisan migrate

This will run the migration, creating the articles table in the database and setting the stage for us to start populating example blog data.

Populating the dummy blog data

Let’s get some test data in place too, so we can see what’s going on more easily. We can set up a Laravel blog seeder to populate our articles table

php artisan make:seeder ArticlesTableSeeder

This’ll make a new file under database\seeders. Edit the run() method so it looks like:

public function run(): void
{
    $faker = Faker::create();

    foreach (range(1, 10) as $index) {
        DB::table('articles')->insert([
            'title' => $faker->sentence(6, true),
            'content' => $faker->paragraphs(3, true),
            'image' => "https://placekitten.com/500/500", //meow              
            'created_at' => now(),
            'updated_at' => now(),
        ]);
    }
}

You also need to make sure that the Faker library is installed via composer

composer require fzaninotto/faker

And include the Faker and DB facades by adding these lines after the namespace declaration

...

use Illuminate\Database\Seeder;

use Illuminate\Support\Facades\DB;
use Faker\Factory as Faker;

class ArticlesTableSeeder extends Seeder
{

...

Now you’re all set up and ready to generate the dummy data. Run this command

php artisan db:seed --class=ArticlesTableSeeder

And if you need to reset the database and start fresh with the same test data, you can run

php artisan migrate:fresh --seed --seeder=ArticlesTableSeeder

The Laravel File Structure

Let’s start setting up all the files we’ll need to create our new blog in Laravel.

Setting up the views

Now we’ve got a database with a few example articles in it, we’re ready to start building the front-end that’ll serve these blog posts to our site’s visitors.

For this tutorial, I’m using Tailwind to style the page. I’ll be including the Tailwind CDN CSS file directly, so no need to install it with Laravel.

To generate the controller and views for our little Laravel blog, run the following command from project root

php artisan make:model Article --controller

This tells Artisan that we’re looking to do two things:

  1. Create an Article Controller for our articles.
  2. Create an Article Model that’ll represent our article data within Laravel

First though, let’s handle the controller

The Article Controller

Take a look at the newly generated file, app\Http\Controllers\ArticleController.php. You should see an empty class, ArticleController.

We’ll need to add an index() method. Add the following methods to the controller

public function index()
{
    $articles = Article::all();
    return view('articles.index', compact('articles'));
}

You’ll also need to inform the controller that it should use the Laravel Model we’ll be generating shortly. Add the following at the top of the file, after the namespace line

...

namespace App\Http\Controllers;

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

class ArticleController extends Controller
{

...

Now we’ll also need to make the relevant views. Thankfully, Taylor Otwell added the make:view command to Artisan recently to make life easier for us developers!

You’ll need to make sure you’re running at least Laravel 10.23.0 to make use of this.

Now, run the following commands seperately to generate the views we’ll use (the --pest argument also creates a handy Feature Test within the /tests directory. We’re not touching that for now, but it’ll be useful in the future!)

We’ll only work with the index and show views here. Later on, we can introduce edit and create views to allow for post editing.

php artisan make:view articles.index --pest
php artisan make:view articles.show --pest

We’ll start by editing the index.blade.php file, newly generated under /resoures/views/articles. Edit the contents of this file so it looks a little like this

@extends('layouts.app')

@section('content')

<div class="container mx-auto">
    <h2 class='my-4 text-2xl'>Article Index</h2>
    <div class="grid grid-cols-3 gap-4">
        @foreach($articles as $article)
            <div class="bg-white p-4 rounded shadow">
                <div class="mb-4">
                    <h3 class="text-xl font-bold mb-4">{{ $article->title }}</h3>
                    <p>{{ Str::limit($article->content, 100) }}</p>
                </div>
                <img src="{{ $article->image }}" alt="{{ $article->title }}" class="w-full h-48 object-cover mb-2">
                <div class="pt-4 font-bold">
                    <a href="/articles/{{ $article->id }}">Read More</a>
                </div>
            </div>
        @endforeach
    </div>
</div>
@endsection

The Layout File

Next, we’ll need to create a layout file which will act as the global container for our app. For our simple blog application, this will be a basic template with some generic styling and a small nav menu.

For simplicity, I’ve also linked to the Tailwind CSS CDN-hosted stylesheet. In the final project, we’d be building our own Tailwind CSS locally.

Create the file resources/views/layouts/app.blade.php and place this code inside it

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Laravel Blog Project</title>

    <!-- Link to Tailwind CSS via CDN for simplicity -->
    <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">

    <!-- If you've set up Tailwind locally, you might link to your compiled CSS file instead -->
    <!-- <link href="{{ asset('css/app.css') }}" rel="stylesheet"> -->
</head>
<body class="bg-gray-100 text-gray-800">

    <nav class="bg-blue-600 text-white p-4">
        <div class="container mx-auto">
            <h1 class="text-2xl font-bold">Laravel Blog Project</h1>
        </div>
    </nav>

    <div class="container mx-auto mt-4 p-4">
        @yield('content')
    </div>

    <footer class="bg-blue-600 text-white p-4 mt-8">
        <div class="container mx-auto text-center">
            &copy; {{ date('Y') }} Laravel Blog Project. All rights reserved.
        </div>
    </footer>

</body>
</html>

The Article Model

Our Article model is very straightforward, and we won’t need to change the file that Artisan has created for us.

All we’re using it for is to create a representation in memory of our Laravel blog database records.

If you want to take a look, the model definition code in app\Models\Article.php looks like this

namespace App\Models;

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

class Article extends Model
{
    use HasFactory;
}

If you continue after this tutorial to expand on the blog functionality and add, for example, editorial control, you’d need to modify the model code to include a $fillable property to let Laravel know that mass assignment will be taking place to the database table.

That’s a security measure in place to guard against known vulnerabities with user generated content.

Aside from that, you’d also need to deal with relationships between models, validation, security.

There’s a lot more to cover when building something like a full Laravel blog app, but we’re going to keep it simple for now!

Setting up Routing

Now, we’ve got a view file for our articles index page, but how do we direct visitors there? Well, we make use of Laravel’s super-handy Routing functionality.

Add the following code to /routes/web.php

use App\Http\Controllers\ArticleController;

Route::resource('articles', ArticleController::class);

This’ll tell Laravel that whenever a visitor hits http://127.0.0.1:8000/articles they should see the generated view that we’ve just created. Give it a try!

Your First Preview

Your development server should now be showing something like this.

What you’re seeing here is the Laravel Blade view that’s been generated via your web route, Article Controller and Model.

Currently, it’s not responsive to user input, but let’s start working some Livewire magic and get our front-end up to speed!

Integrating Livewire

To make sure that our layout knows about Livewire and can react to our input events, let’s make a few small changes.

First, edit resources/views/layouts/app.blade.php and add the following:

  1. @livewireStyles just before the closing </head> tag
  2. @livewireScripts just before the closing </body> tag
    ....

    @livewireStyles

</head>
<body class="bg-gray-100 text-gray-800">

    ....

    @livewireScripts

</body>
</html>

Next, run the command

php artisan make:livewire ArticlesIndex

This will create our first Livewire component, which we’ll be using to show the list of articles.

This will generate two new files:

  • app/Livewire/ArticlesIndex.php (the component’s class)
  • resources/views/livewire/articles-index.blade.php (the component’s view)

Paginating Blog Posts

Let’s make use of pagination functionality so we can easily navigate through the list of blog posts.

This will be fairly standard stuff – “next” and “previous” buttons and a list of pages. We’ll set up our article view to show 6 blog posts on each page.

In the ArticlesIndex.php file, you can make use Laravel’s built-in pagination to simplify this task, update the file like so

namespace App\Livewire;

use Livewire\Component;
use App\Models\Article;
use Livewire\WithPagination;

class ArticlesIndex extends Component
{
    use WithPagination;

    public function render()
    {
        return view('livewire.articles-index', [
            'articles' => Article::latest()->paginate(6)
        ]);
    }
}

Then in resources/views/livewire/articles-index.blade.php, display the articles and add pagination

<div>
    <div class="grid grid-cols-3 gap-4">
        @foreach($articles as $article)
            <div class="bg-white p-4 rounded shadow">
                <div class="mb-4">
                    <h3 class="text-xl font-bold mb-4">{{ $article->title }}</h3>
                    <p>{{ Str::limit($article->content, 100) }}</p>
                </div>
                <img src="{{ $article->image }}" alt="{{ $article->title }}" class="w-full h-48 object-cover mb-2">
                <div class="pt-4 font-bold">
                    <a wire:navigate href="/articles/{{ $article->id }}">Read More</a>
                </div>
            </div>
        @endforeach
    </div>

    <div class="mt-4">
        {{ $articles->links() }}
    </div>
</div>

We’re now going to modify our existing Article index view template, resources/views/articles/index.blade.php. Remove everything from this file and replace with the much simpler layout

@extends('layouts.app')

@section('content')
    <h2 class='my-4 text-2xl'>Article Index</h2>
    @livewire('articles-index')
@endsection

Try It Out

This should be everything needed to power our new Livewire-enabled blog layout. Take another look at your development server.

It should be looking a little like this.

Fairly similar, but now see the new pagination options in the bottom right?

You should be able to click on them and see the blog posts seamlessly flip between pages 1 and 2. This is all taking place on the client side with requests to the server happening in the background.

If you inspect the network activity while you’re navigating the page, you’ll see these invisible page requests.

And all of this has happened without you touching a single line of a front-end reactive framework!

Showing The Blog Post

Now that we’ve sorted out our blog index page, it should be straightforward enough to get the “Read More” link working to show us the full blog article.

Let’s follow the same process to create a Livewire component to show us a single blog page

php artisan make:livewire ArticleShow

This will create another two files for us

  • app/Livewire/ArticleShow.php (the component’s class)
  • resources/views/livewire/article-show.blade.php (the component’s view)

We only need to make a few changes to ArticleShow.php

  • Include our Article model
  • Make use of Livewire’s Mount functionality so that the view is aware of the data

(Note that Livewire should automatically assign the article data to public $article but the mount() method can be used to ensure this happens)

You can think of mount() in the same way as the standard PHP __construct() class constructor method.

namespace App\Livewire;

use Livewire\Component;
use App\Models\Article;

class ArticleShow extends Component
{
    public $article;

    public function mount(Article $article)
    {
        $this->article = $article;
    }

    public function render()
    {
        return view('livewire.article-show');
    }
}

This controller simply renders the appropriate view file, article-show.blade.php

And inside article-show.blade.php, add the following code

<div>
    <h2 class="text-2xl mb-4">{{ $article->title }}</h2>
    <p>{{ $article->content }}</p>
    <div class='mt-8 font-bold'>
        <a wire:navigate href="/articles">Back to articles</a>
    </div>
</div>

Note the additional link at the end which we’ll use to navigate back to the article list

We also need to route the front-end requests to this new Livewire component. Update the routes\web.php file so it contains this

...

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

use App\Livewire\ArticlesIndex;
use App\Livewire\ArticleShow;

Route::get('/articles', ArticlesIndex::class);
Route::get('/articles/{article}', ArticleShow::class);

And there’s one more file to create now. We still need to create a layout file for full page components to render into. Add a new file, resources/views/components/layouts/app.blade.php

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Laravel Blog Project</title>

    <!-- Link to Tailwind CSS via CDN for simplicity -->
    <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">

    <!-- If you've set up Tailwind locally, you might link to your compiled CSS file instead -->
    <!-- <link href="{{ asset('css/app.css') }}" rel="stylesheet"> -->

    @livewireStyles

</head>
<body class="bg-gray-100 text-gray-800">

    <nav class="bg-blue-600 text-white p-4">
        <div class="container mx-auto">
            <h1 class="text-2xl font-bold">Laravel Blog Project</h1>
        </div>
    </nav>

    <div class="container mx-auto mt-4 p-4">
        {{ $slot }}
    </div>

    <footer class="bg-blue-600 text-white p-4 mt-8">
        <div class="container mx-auto text-center">
            &copy; {{ date('Y') }} Laravel Blog Project. All rights reserved.
        </div>
    </footer>

    @livewireScripts    

</body>
</html>

This is very similar to our original layouts/app.blade.php layout except we’re now using {{ $slot }} instead of the Laravel yield directive since that’s the method Laravel uses for embedding content into components.

Go back to your development server once more and you should see the following after clicking on a “Read More” link

Perhaps the least inspiring blog design you’ve ever seen! (there’s a lot of room for improvement 😂)

That’s all for now

This tutorial should have given you a decent understanding of the basic fundamentals of Livewire. Go explore and see what else you can add to the blog app.

How Does It Work?

Full Page Components

So what’s going on here?

We’re making use of a component rendering technique known as “full page components” within Livewire to build out our Laravel blog.

We’re essentially rendering two completely seperate components via the two routes /articles and (e.g.) /articles/3 – the wire:navigate

Whenever we load up /articles or /articles/3 in our browser, Laravel makes a server-side call to render this layout with the appropriate components in place. (this is vital from an SEO point of view too, as it means search engines will also have a rapidly generated HTML page to process)

But after this point, whenever a user clicks on a link to perform an action, an AJAX call is sent out to fetch new HTML – this is either

  1. The full page HTML for another view, e.g. swapping between the articles index and the individual blog post
  2. When clicking through the pagination pages to refresh the state of the articles index

So you can see how by using Livewire’s components, we can perform some rather clever tricks after the intial page load is complete.

What’s Going On Behind The Scenes?

The way that Livewire turns a server-side rendered app into a website that can use client-side routing to create a “single page app” experience isn’t that complicated:

  1. Livewire renders the inital page load with the relevant content loaded into a Blade template, so it’s SEO friendly
  2. Whenever an interaction occurs (like a button click on a pagination link), an AJAX request occurs in the background
  3. The component is updated with the fresh data (such as the next page of blog articles)
  4. The front-end DOM is then intelligently updated according to the refreshed data so the state remains coherent

Future Improvements

  • Currently we’re relying on the use of Full Page Components to refresh the entire page when clicking to/from an article and back to the index. This feels messy.
  • With some further work and a bit of additional JavaScript, we could add event listeners that would respond intelligently to reload only parts of the page when navigating through the site. That way, we could build an app that acts more like a software application UI than a website
  • We could add user authentication and permit post editing via the use of other Livewire features such as actions which make handling user input more straightforward
  • We can integrate Tailwind fully to allow for more stylesheet customisation

Insights #1 : Livewire Cached Computed Properties

One of the coolest features of Livewire is its ability to cache computed properties. This is perfect for pulling in articles or any other data that doesn’t change often. This is especially useful for a Laravel blog where we’re not expecting the content of an article to update often.

Let’s say you have an Article model. Here’s how you can cache its properties:

use Livewire\Component;

class ShowArticles extends Component
{
    public function getArticlesProperty()
    {
        return cache()->remember('articles', 3600, function () {
            return Article::all();
        });
    }

    public function render()
    {
        return view('livewire.show-articles', ['articles' => $this->articles]);
    }
}

In the example above, Livewire will cache the articles for an hour (3600 seconds). This means super-fast data retrieval!

Try implementing that in our Article post type above

Insights #2 : Livewire Navigation: wire:navigate

You’ll have noticed the use of wire:navigate in the Laravel blog code. This is a really handy feature in Livewire and enables transitions between pages to happen without a page reload.

As above, we can consider situations where we’d want to reload only sections of a page, but out-of-the-box Livewire enables websites to skip page reloads entirely, allowing seamless navigation between all sections of a site.

For our example, we might want three pages: main, about, and contact.

// routes/web.php
Route::get('/', MainComponent::class);
Route::get('/about', AboutComponent::class);
Route::get('/contact', ContactComponent::class);

Now, in your blade view, you can use wire:navigate for navigation:

<a wire:navigate href="/about">About Us</a>
<a wire:navigate href="/contact">Contact Us</a>

This ensures that when a user clicks on these links, the page loads are lightning-fast!

If you feel like expanding the blog app out into a full website, you can use wire:navigate to let the user jump between pages with ease.

Coming Soon

Part 2 of this tutorial – in which I’ll explore how we can use Livewire’s powerful capabilities to introduce nested navigation, post editing and user authentication!
Author Image

About the author

Robin is the dedicated developer behind Solarise.dev. With years of experience in web development, he's committed to delivering high-quality solutions and ensuring websites run smoothly. Always eager to learn and adapt to the ever-changing digital landscape, Robin believes in the power of collaboration and sharing knowledge. Outside of coding, he enjoys diving into tech news and exploring new tools in the industry.

If you'd like to get in touch to discuss a project, or if you'd just like to ask a question, fill in the form below.

Get in touch

Send me a message and I'll get back to you as soon as possible. Ask me about anything - web development, project proposals or just say hi!

Please enable JavaScript in your browser to complete this form.

Solarise.dev Services

Maintenance Retainers

Min 3 hours per month, no contract

Project Planning

Get in touch to discuss your project details

Consultancy

Face-to-face chats. Solve your problems!