Learn Livewire 3, Volt, and Folio by building a podcast player


HomeHome / News / Learn Livewire 3, Volt, and Folio by building a podcast player

May 24, 2023

Learn Livewire 3, Volt, and Folio by building a podcast player

Yesterday, the Laravel team released Laravel Folio - a powerful page-based router designed to simplify routing in Laravel applications. Today, they released Volt -an elegantly crafted functional API

Yesterday, the Laravel team released Laravel Folio - a powerful page-based router designed to simplify routing in Laravel applications. Today, they released Volt -an elegantly crafted functional API for Livewire, allowing a component's PHP logic and Blade templates to coexist in the same file with reduced boilerplate.

Although they may be used separately, I think using them together is a new, incredibly productive way to build Laravel apps.

In this article, I will teach you how to build a simple app that lists out episodes of the Laravel News podcast and allows users to play them, with a player that can seamlessly continue playing across page loads.

To get started, we need to create a new Laravel app and install Livewire, Volt, Folio, and Sushi (to make some dummy data).

Livewire v3, Volt, and Folio are all still in beta. They should be pretty stable, but use them at your own risk.

After requiring the packages, we need to run php artisan volt:install and php artisan folio:install. This will scaffold out some folders and service providers Volt and Folio need.

For dummy data, I'm going to create a Sushi model. Sushi is a package written by Caleb Pozio that allows you to create Eloquent models that query their data from an array written directly in the model file. This works great when you're building example apps or have data that doesn't need to change very often.

Create a model, then remove the HasFactory trait and replace it with the Sushi trait. I added the details of the 4 latest Laravel News Podcast episodes as the data for this example.

I won't go into detail on how all this works since this isn't the point of the article, and you'll likely use a real Eloquent model if you were to build your own podcast player.

We'll need a layout file to load Tailwind, add a logo, and add some basic styling. Since Livewire and Alpine automatically inject their scripts and styles now, we don't even need to load those in the layout! We'll create the layout as an anonymous Blade component at resources/views/components/layout.blade.php.

First, we need a page to display all the episodes of the podcast.

Using Folio, we can easily create a new page in the resources/views/pages directory, and Laravel will automatically create a route for that page. We want our route to be /episodes, so we can run php artisan make:folio episodes/index. That will create a blank view at resources/views/pages/episodes/index.blade.php.

On this page, we'll insert the layout component, then loop over all the podcast episodes. Volt provides namespaced functions for most of the Livewire features. Here, we'll open regular <?php ?> open and close tags. Inside those, we'll use the computed function to create an $episodes variable that runs a query to get all the Episode models ($episodes = computed(fn () => Episode::get());). We can access the computed property in the template using $this->episodes.

I also created a $formatDuration variable that's a function to format each episode's duration_in_seconds property to a readable format. We can call that function in the template using $this->formatDuration($episode->duration_in_seconds).

We also need to wrap the dynamic functionality on the page in the @volt directive to register it as an "anonymous Livewire component" within the Folio page.

From there, we need to add some interactivity. I want to add an episode player so we can listen to the episodes from the episode list. This can be a regular Blade component we render in the layout file.

We can create that component by adding a resources/views/components/episode-player.blade.php file. Inside the component, we'll add an <audio> element with some Alpine code to store the active episode and a function that updates the active episode and starts the audio. We'll only show the player if an active episode is set, and we'll add a nice fade transition to the wrapper.

If we reload the page, we don't see any changes. That's because we haven't added a way to play episodes. We'll use events to communicate from our Livewire components to the player. First, in the player, we'll add x-on:play-episode.window="play($event.detail)" to listen for the play-episode event on the window, then call the play function.

Next, back in the episodes/index page, we'll add a click listener on the play buttons for each episode. The buttons will dispatch the play-episode event, which will be received by the episode player and handled there.

Next, I'd like to add an episode details page to display each episode's show notes and other details.

Folio has some pretty cool conventions for route model binding in your filenames. To make an equivalent route for /episodes/{episode:id}, create a page at resources/views/pages/episodes/[Episode].blade.php. To use a route parameter other than the primary key, you can use the [Model:some_other_key].blade.php syntax in your filename. I want to use the episode number in the URL, so we'll create a file at resources/views/pages/episodes/[Episode:number].blade.php.

Folio will automatically query the Episode models for an episode with the number we pass in the URL and make that available as an $episode variable in our <?php ?> code. We can then convert that to a Livewire property using Volt's state function.

We'll also include a play button on this page so users can play an episode while viewing its details.

Now, we need to link to the details page from the index page. Back in the episodes/index page, let's wrap each episode's <h2> in an anchor tag.

We're almost there. The app looks pretty good and functions well, but there's one issue. If a user is listening to an episode, and navigates to a different page, the episode player loses its active episode state and disappears.

Thankfully, Livewire has the wire:navigate and the @persist directive to help with these problems now!

In our layout file, let's wrap the logo and episode player in @persist blocks. Livewire will detect this and skip re-rendering those blocks when we change pages.

Finally, we need to add the wire:navigate attribute to all the links through the app. For example:

When you use the wire:navigate attribute, behind the scenes, Livewire will fetch the new page's contents using AJAX, then magically swap out the contents in your browser without doing a full page reload. This makes page loads feel incredibly fast and enables features like persist to work! It enables features that previously you could only accomplish by building a SPA.

This was a really fun demo app to build while learning Volt and Folio. I've uploaded the demo app here and @bosunski created a phpsandbox if you want to see the full source code or try it out yourself!

What do you think? Is Livewire v3 + Volt + Folio the simplest stack for building Laravel apps now? I think it's really cool and might feel more familiar to people who are used to building apps in JavaScript frameworks like Next.js and Nuxt.js. It's also nice to have all your code for a page collocated - styling (via Tailwind), JS (via Alpine), and backend code all in one file. Send me your thoughts on Twitter!

Filed in:

Jason Beggs

TALL stack (Tailwind CSS, Alpine.js, Laravel, and Livewire) consultant and owner of roasted.dev.