Laravel Volt realtime live stats
Hey! So Livewire 3 and Volt are out. I’ve been playing around with them and they’re pretty cool. My favorite part? Definitely the long …
ReadI’ve always been interested in real-time technologies, especially when dealing with high traffic and complex issues. I once encountered a situation with an e-commerce store’s operation team. They faced an issue where multiple operators would handle a single order. Their initial solution was to create an auto-assign system, which aimed to distribute orders equally among operators. However, this system faced challenges when operators went on vacation or when an operator had a few particularly difficult orders to manage. So the first solution I thought of is something similar to how google docs shows you who else is reading/modifying the content in realtime.
My suggestion was to begin by informing the team if someone else is already viewing an order when they open it. This way, they can assign orders to themselves without overlap.
I’ve set up a demo to show you how this can be done, and I’ll explain it step by step. But before we dive in, let’s give this feature a name. I’ve decided to call it “On this page.”
The e-commerce admin pages are written in Blade and few little javascript. So I could’ve implemented it with a websocket server and few javascript to display the active users on every page. But Instead I used already the existing stack Laravel & Redis + Livewire.
The operations team isn’t very big, with about 50 people. We can use Redis to track which page each person is viewing. We’ll label this information with a unique page identifier. On the page, we can then show the pictures of the users viewing it. Additionally, we can set a regular interval to update and show any new users who start viewing the page.
We can bring this idea to life using a single Livewire component:
“On this page” Livewire Component
On Mount: Use the route name combined with its original parameters to create a unique key for the route.
On Render: Do two things:
Auto-refresh: Utilize Livewire’s polling feature to refresh the component every set interval.
We chose Redis for this task. It’s perfect as it can handle a vast number of keys that auto-expire and offers the flexibility to retrieve keys on-the-go.
As of writing this article, Livewire 3 is still in beta. However, the code I’ll share is compatible with both version 2 and 3 since we aren’t using any exclusive features from v3. For this tutorial, I’m using v3.
1composer require livewire/livewire "^3.0@beta"
Now let’s start creating our new component
php artisan make:livewire OnThisPage
This will create 2 files
Now, let’s dive into the main implementation. First, we’ll begin by obtaining the unique name for the route.
1class OnThisPage extends Component
2{
3 public $routeName;
4
5 public $durationInSeconds = 10;
6
7 public function mount(Request $request)
8 {
9 $this->routeName = sprintf(
10 '%s:%s',
11 $request->route()->getName(),
12 implode(':', $request->route()->originalParameters())
13 );
14 }
15}
In our approach, we’ll get the unique route name in the mount
method. If we try to do this in the render
method, the route name will switch to livewire.update
. This happens because when the component auto-refreshes using Livewire’s polling
feature, Livewire sends a request to the livewire.update
route to fetch the component’s details, avoiding a full page reload.
logOnThisPage
method:1private function logOnThisPage(Request $request): void
2{
3 $userId = $request->user()->id;
4 $page = $this->routeName;
5 $key = "{$page}:{$userId}";
6
7 Redis::setex($key, $this->durationInSeconds, $userId);
8}
This method fetches the user ID and the unique route name. It then combines them to form a key for Redis, which is set to expire after 10 seconds.
getUserIdsOnThisPage
helper method to retrieve the IDs of users currently viewing the page:1/** @return array<int> */
2private function getUserIdsOnThisPage(Request $request): array
3{
4 $page = $this->routeName;
5
6 return collect(Redis::keys("{$page}:*"))
7 ->map(fn($key) => str_replace("{$page}:", '', $key))
8 ->toArray();
9}
This method gathers all keys that begin with the route name as a prefix. It then extracts just the user ID from each key.
We opt for the Redis::keys
method over Redis::scan
because the maximum number of users on a page is 50 (the total number of operation team members). This is a manageable number for our app’s memory.
render
method, which is the final method in our component: 1public function render(Request $request)
2{
3 $this->logOnThisPage($request);
4
5 $users = User::query()
6 ->select('id', 'name', 'username', 'avatar_url')
7 ->whereIn('id', $this->getUserIdsOnThisPage($request))
8 ->get();
9
10 return view('livewire.on-this-page', [
11 'users' => $users,
12 'duration' => $this->durationInSeconds,
13 ]);
14}
First, we log the current user with the OnThisPage
function. Then, we retrieve user details from the database for display.
Now, let’s tackle the frontend. In this section, we’ll loop through the list of users to display their photos. We’ll also utilize polling to automatically update the component every 20 seconds, based on the duration variable.
1<div wire:poll.{{ $duration }}s>
2 <div class="flex overflow-hidden items-center">
3 <div class="text-gray-400">
4 On this page
5 </div>
6 <div class="ml-2 flex -space-x-2">
7 @foreach($users as $user)
8 <img class="inline-block h-8 w-8 rounded-full text-white shadow-solid border border-black"
9 src="{{ asset($user->avatar_url) }}" alt="{{ $user->name }}"/>
10 @endforeach
11 </div>
12 </div>
13</div>
This template simply showcases which users are currently on the page by displaying their profile photos.
To use this component, just integrate it into the pages where you want the users to see who else is viewing the same page.
For demonstration, I integrated it into a basic CMS. In both the posts.list
and posts.show
views, I added this component to the header:
1<div>
2 <livewire:on-this-page />
3</div>
This will allow team members to quickly see who else is looking at the same content.
Polling Overhead: We set a specific time for caching and for the Livewire component to check regularly. Even though checking every few seconds helps keep things up-to-date, it can also put extra work on the system. So, it’s essential to watch the set time and see how it impacts your system.
Memory Consideration: The system is tailored for an operations team of about 50 members, but as the user base grows, so does memory consumption. This is particularly noticeable if the expiration time for Redis keys is lengthened. This growth in memory usage could impact the performance and cost-efficiency of the application if not adequately monitored and managed.
UI Clutter: Displaying a full list of users can make the interface look congested, especially when there are more than five users. It might be beneficial to set a display limit or introduce a popup that shows the complete list only when necessary.
Database Hits: The way our system is set up, it often asks the database for user info because of its regular checks. When lots of users are active, this can make the database work harder. If we don’t fix this over time, it can slow things down or cause other problems. A solution might be to store user data in Redis so we don’t have to ask the database so often.
How do you feel about this approach? Have you ever needed something similar? If so, how did you implement it? We’d love to hear your thoughts or any alternative methods you’ve tried. Share your experiences and feedback on this article with us!
Happy Coding!
Hey! So Livewire 3 and Volt are out. I’ve been playing around with them and they’re pretty cool. My favorite part? Definitely the long …
ReadIn your Laravel projects, how do you go about setting up routes for a particular resource? Do you do it in the following manner? …
Read