Real-time applications are a great way to keep users on a site for longer periods of time. They also provide an incredibly smooth experience for users interacting with your web application. In this article, we will learn how to make use of real-time technologies and create incredible user experiences with the Laravel Framework.

What we'll build

Imagine you are building an application that notifies users when something happens in real time. By leveraging the existing Laravel architecture, we can build this out fairly easily. Here are the steps we will follow:

  • Set up a new Laravel application with some boilerplate code.
  • Create a local WebSocket server that receives and pushes events to our authenticated user.
  • Create specific notification classes that house the data for each notification.
  • Display our notification in real time on our application.

The final code for this tutorial is available on GitHub.

Let's dive in

For this tutorial, I will assume that you already know the fundamentals of Laravel and can set up a new project on your own. However, we will begin by creating a new Laravel application and add in some boilerplate code to get started:

composer create-project laravel/laravel realtime
cd realtime
composer require laravel/breeze --dev
php artisan breeze:install vue

Now that we have set up a simple application using Laravel Breeze with Inertia and Vue, we’ll create a dummy user for testing. Go to the /database/seeders/DatabaseSeeder.php class and create a fake user with an easy-to-remember email address for testing purposes.

<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    public function run()
    {
        \App\Models\User::factory()->create([
            'name' => 'Test User',
            'email' => 'test@example.com',
            'password' => bcrypt('password'),
        ]);
    }
}

Ensure that your database is set up, and then run the following:

php artisan migrate:fresh --seed 

Once this is run, you should be able to log in with the user we just created and see the default dashboard:

Default Dashboard

Preparing the user model

For the purposes of this demo, we’ll need to prepare the user model to receive notifications. Laravel ships with a Notifiable trait on the user model by default, which makes it easy to get started. The simplest way for us to save notifications for a given user is to use the database, which is also included out-of-the-box. All we need to do is prepare the database to handle this task:

php artisan notifications:table
php artisan migrate

This command will create a new migration that will allow us store notifications for a specific user in the database. To do this, we will need to create a new Notification class and prepare it to save the notification when called.

php artisan make:notification SomethingHappened

This command will create a new notification class in app/Notifications/SomethingHappened.php; we’ll need to amend the default boilerplate to instruct Laravel to store the notification in the database.

<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification;

class SomethingHappened extends Notification
{
    use Queueable;

    public function __construct(public string $text)
    {}

    public function via($notifiable)
    {
        return ['database'];
    }

    public function toArray($notifiable)
    {
        return [
            'text' => $this->text,
        ];
    }
}

Great! Now we are ready to perform some testing.

Triggering notifications

To test the triggering of notifications, we will create a simple route that, when run in Laravel, will trigger a notification for our test user. Let's head over to our /routes/web.php file and add the route:

Route::get('/notify/{text}', function (string $text) {
    $user = User::where('email', 'test@example.com')->first();
    $user->notify(new SomethingHappened($text));
});

NB: This is purely for demo purposes and is not intended for use in a production system.

Now when we navigate to http://realtime.test/notify/hello in our browser, it will fire the notification and save it to the database. Open the database in your preferred database tool and see it here:

Database

This means that it is working! Great!

Connecting the frontend

Now that we have the notifications stored in the database, we want to display them in our frontend. To do this, we will need to load the notifications into the View and display them using Vue.js. Let's once again head over to our /routes/web.php file and change the boilerplate code for the dashboard route. All we need to do is pass through our notifications to the dashboard so that we can display them.

Route::get('/dashboard', function () {
    return Inertia::render('Dashboard', [
        'notifications' => \request()->user()->notifications
    ]);
})->middleware(['auth', 'verified'])->name('dashboard');

Note: This is purely for demo purposes; in a production application, you would share this through Inertia's global share function.

After the notifications are being passed, we can edit the /resources/js/Pages/Dashboard.vue file to display them:

<template>
    <Head title="Dashboard" />
    <BreezeAuthenticatedLayout>
        <template #header>
            <h2 class="font-semibold text-xl text-gray-800 leading-tight">
                Dashboard
            </h2>
        </template>
        <div class="py-12">
            <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
                <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
                    <div
                        v-for="notification in notifications"
                        :key="notification.id"
                        class="flex items-center bg-blue-500 text-white text-sm font-bold px-4 py-3">
                        <p v-text="notification.data.text"/>
                    </div>
                </div>
            </div>
        </div>
    </BreezeAuthenticatedLayout>
</template>
<script setup>
import BreezeAuthenticatedLayout from '@/Layouts/Authenticated.vue';
import { Head } from '@inertiajs/inertia-vue3';
import {defineProps} from "vue";
const props = defineProps({
    notifications: {
        type: Array,
        required: true,
    }
})
</script>

The above code will output the notification we fired to the dashboard, as shown here:

Frontend

Making this real-time

Now that we’ve displayed the notifications in our frontend, we need to find a way to have the notifications broadcast when fired to the frontend, have the frontend listen for the broadcast event, and then simply reload the notifications array.

To do this, we will need to pull in two packages:

composer require beyondcode/laravel-websockets
composer require pusher/pusher-php-server

These two packages will allow us to create a simple WebSocket server that we can broadcast events to and from. To get this to work correctly, we will need to update our .env files to house the following variables:

BROADCAST_DRIVER=pusher
PUSHER_APP_ID=123456789
PUSHER_APP_KEY=123456789
PUSHER_APP_SECRET=123456789
PUSHER_APP_CLUSTER=mt1

Note that the keys can be simple for local testing; in production environments, I recommend making them stronger.

Now that we have set up the WebSocket server, we can run the server using the commands provided by the WebSocket package:

php artisan vendor:publish --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider" --tag="config"

php artisan vendor:publish --provider="BeyondCode\LaravelWebSockets\WebSocketsServiceProvider" --tag="migrations"
php artisan migrate
php artisan websocket:serve

Navigate to http://realtime.test/laravel-websockets, where we can see a new dashboard that will show incoming events pushed to the socket server.

WebSocket Dashboard

You will need to click connect, which will show some more debugging information, as shown below:

Connected Websocket Dashboard

Broadcasting the notification

Now that we have the WebSocket set up, we will need to make a few changes to our notification class to instruct it to broadcast to our WebSocket server:

  • Implement the ShouldBroadcast interface.
  • Add a new via method called broadcast.
  • Add a toBroadcast() methood that returns a BroadcastMessage class.
<?php

namespace App\Notifications;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Notifications\Messages\BroadcastMessage;
use Illuminate\Notifications\Notification;

class SomethingHappened extends Notification implements ShouldBroadcast
{
    use Queueable;

    public function __construct(public string $text)
    {}

    public function via($notifiable)
    {
        return ['database', 'broadcast'];
    }

    public function toArray($notifiable)
    {
        return [
            'text' => $this->text,
        ];
    }

    public function toBroadcast($notifiable): BroadcastMessage
    {
        return new BroadcastMessage([
            'text' => $this->text
        ]);
    }
}

Finally, to get our WebSocket server working as expected, we will need to update our application to make use of our newly installed WebSocket server. To do this, we can edit our config/broadcasting.php file and replace the default pusher array to look like this:

'pusher' => [
    'driver' => 'pusher',
    'key' => env('PUSHER_APP_KEY'),
    'secret' => env('PUSHER_APP_SECRET'),
    'app_id' => env('PUSHER_APP_ID'),
    'options' => [
        'cluster' => env('PUSHER_APP_CLUSTER'),
        'encrypted' => false,
        'host' => '127.0.0.1',
        'port' => 6001,
        'scheme' => 'http'
    ],
],

This should do the trick, and by testing our WebSocket server, we can start to see debugging information coming through!

Working Example

As you can see, whenever I reload the test route, a new message is pushed to our server. This is great! All we need to do now is connect the frontend, and we will be good to go.

Connecting the frontend

Now that we have the messages being pushed to our WebSocket server, all we need to do is listen for those events directly in our frontend application. Laravel provides a handy way to achieve this with Laravel Echo. Let's install and set it up now.

npm install --save-dev laravel-echo pusher-js

And after it is installed, we will need to configure it in /resources/js/bootstrap.js:

import Echo from 'laravel-echo';
import Pusher from 'pusher-js';
window.Pusher = Pusher;
window.Echo = new Echo({
    broadcaster: 'pusher',
    key: import.meta.env.VITE_PUSHER_APP_KEY,
    cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER,
    wsHost: window.location.hostname,
    wsPort: 6001,
    forceTLS: false,
});

Head over to /resources/js/Pages/Dashboard.vue and update the code to look like this:

<template>
    <Head title="Dashboard" />
    <BreezeAuthenticatedLayout>
        <template #header>
            <h2 class="font-semibold text-xl text-gray-800 leading-tight">
                Dashboard
            </h2>
        </template>
        <div class="py-12">
            <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
                <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
                    <div
                        v-for="notification in notifications"
                        :key="notification.id"
                        class="mt-3 flex items-center bg-blue-500 text-white text-sm font-bold px-4 py-3">
                        <p v-text="notification.data.text"/>
                    </div>
                </div>
            </div>
        </div>
    </BreezeAuthenticatedLayout>
</template>
<script setup>
import BreezeAuthenticatedLayout from '@/Layouts/Authenticated.vue';
import { Head } from '@inertiajs/inertia-vue3';
import { Inertia } from '@inertiajs/inertia'
import {defineProps, onMounted} from "vue";
const props = defineProps({
    notifications: {
        type: Array,
        required: true,
    }
})
onMounted(() => {
    Echo.private('App.Models.User.1')
        .notification((notification) => {
            console.log('reloading')
            Inertia.reload()
        });
})
</script>

If we reload the page now, we will see a console error that looks like this:

Console Error

This is completely normal because we are using a private channel, and Laravel excludes the broadcasting routes until needed by default. To fix this error, we can simply open config/app.php and ensure the BroadcastingService Provider is enabled (by default, it is commented out).

/*
 * Application Service Providers...
 */
App\Providers\AppServiceProvider::class,
App\Providers\AuthServiceProvider::class,
App\Providers\BroadcastServiceProvider::class,
App\Providers\EventServiceProvider::class,
App\Providers\RouteServiceProvider::class,

Once this is done, it should do the trick! Let's open up our browser and see how this all works!

Final Demo

Conclusion

As you can see, Laravel provides a simple, clean, and scalable way to interact with real-time events in your application. Although this demo is very basic and straightforward, the applications for this are endless, and developers can build efficient user experiences with just a few lines of code.

Get the Honeybadger newsletter

Each month we share news, best practices, and stories from the DevOps & monitoring community—exclusively for developers like you.
    author photo
    Devin Gray

    Laravel Enthusiast… Part Time Human Being

    More articles by Devin Gray
    An advertisement for Honeybadger that reads 'Turn your logs into events.'

    "Splunk-like querying without having to sell my kidneys? nice"

    That’s a direct quote from someone who just saw Honeybadger Insights. It’s a bit like Papertrail or DataDog—but with just the good parts and a reasonable price tag.

    Best of all, Insights logging is available on our free tier as part of a comprehensive monitoring suite including error tracking, uptime monitoring, status pages, and more.

    Start logging for FREE
    Simple 5-minute setup — No credit card required