Laravel Reverb: A Comprehensive Guide to Real-Time Broadcasting

February 11, 2025
Written by
Prosper Ugbovo
Contributor
Opinions expressed by Twilio contributors are their own
Reviewed by

Laravel Reverb: A Comprehensive Guide to Real-Time Broadcasting

It is the 1950s and you are a football fan. The big game is on, but you're not at the stadium. What are your options? Wait for tomorrow's newspaper or listen to a radio recap later. There is no instant gratification or real time thrill. The game occurs, and you are left in the dark, waiting.

Now, fast forward to the early 1990s. Live television broadcasts have become the norm. You're watching the big game in real time from the comfort of your living room. Every goal, save, and dramatic moment is captured in real time. The contrast is night and day. You are no longer a passive receiver of old news but an active player in the ongoing drama.

This transition parallels the current progress of web development. For years, web applications mirrored the pre-broadcast era: static, update-on-refresh experiences in which users were constantly one step behind the real happenings. However, we are now in the age of real-time web apps, in which changes are delivered instantaneously and users are constantly up to speed with the most recent data.

What is Laravel Reverb?

Laravel Reverb is to web applications what live broadcasting is to sports coverage. Written in core PHP, it's a WebSocket server designed to work seamlessly with Laravel's event broadcasting system, bringing real-time capabilities to your web applications.

Just as live broadcasting transformed passive viewers into engaged participants, Reverb transforms static web pages into dynamic, instantly updating applications. It provides the infrastructure to push real-time updates from your server to connected clients, as the crucial link between your Laravel backend and the user's browser.

By the end of this article, you’ll know how to implement Laravel Reverb to broadcast live updates across your applications, allowing for dynamic user experiences.

Prerequisites

  • Prior knowledge of Laravel and PHP
  • PHP 8.2 or newer
  • Composer and Node.js globally installed
  • Your preferred text editor or IDE

Set up the project

Let's start by setting up a new Laravel project. Open a terminal window, create a new Laravel application, and navigate into it with the following commands:

composer create-project laravel/laravel todo
cd todo

Next, you need to install broadcasting, because since Laravel 11, broadcasting is not enabled in new Laravel applications by default.

php artisan install:broadcasting

While the command runs, you will be prompted to install Laravel Reverb and Node dependencies. Install them by answering "yes" at the terminal. On inputting "yes", the command will run php artisan reverb:install to publish the settings, add the needed environment variables, and enable event broadcasting in your application.

Several things happened when you were installing broadcasting in your Laravel application:

  • The config/broadcasting.php file was created, which contains the broadcasting module configurations
  • The routes/channels.php file has also been generated. You use it to set up your application's broadcast authorization routes and callbacks
  • Reverb was installed
  • Finally, Laravel Echo and pusher.js were added as node dependencies, and resources/js/echo.js which connects Laravel echo to the Reverb server was also created
Before broadcasting any event, you must set up and run a queue worker. By default, the database serves as the queue driver.

Understand the core concepts

Some fundamental broadcasting concepts must be understood to properly understand how Laravel Reverb can be used to enable real-time functionality in your apps. These include channels, events, listeners, and web sockets.Together, these components enable the smooth data flow from the server to the client, allowing for real-time interactions without frequent refreshing.

Let’s break these concepts down step by step.

Events

In Laravel, events are the primary data units broadcast between channels. When anything occurs in your application that you want to communicate to the client (for example, a new message is delivered, a person joins a group, or a notice is triggered), you generate an event. This event comprises pertinent data, such as message content or user information, and is broadcast to a channel.

Laravel simplifies handling events by giving means for creating them. Events can refer to anything meaningful that happens in your application, and an event class contains the logic that determines what data is broadcast and on which channel.

Let’s say you’re building a chat application. When a user sends a message, you create an event named MessageSentEvent that carries the message data and broadcasts it to a chat channel.

Broadcasting Channels

Imagine you’re tuning into a radio station. The station broadcasts music, and everyone tuned to that frequency can listen in. In Laravel's event broadcasting, channels work similarly. When you want to broadcast an event, you send it through a channel and everyone “listening” to that channel can hear it.

There are three types of channels in Laravel:

Public channels

Public channels are accessible to anyone. When you broadcast an event on a public channel, any connected client can listen to that event without authentication.

Private channels:

Private channels add a layer of security by requiring users to be authenticated before they can listen to events. When a private channel is used, Laravel ensures that only authenticated users with proper permissions can access the broadcast. Imagine a chat app where users have their private messages. You wouldn’t want everyone to see those messages, right? Broadcasting on a private channel ensures only the intended user can receive those messages.

Presence channels:

Presence channels extend private channels by also tracking who is actively connected. In presence channels, you broadcast data and have real-time knowledge of who’s subscribed to the channel. This allows developers to build features that require user presence tracking, such as chat rooms where users can see who is online. Presence channels are perfect for real-time collaborative applications, like a shared document editing tool or an online chatroom where you want to display an online/offline status.

Listeners

While events are pushed from the server side, listeners are what handle the broadcast on the client side. Listeners are JavaScript components that subscribe to a specific channel and "listen" for events broadcast on that channel. When an event is broadcast, the listener reacts to it and performs some action, such as updating the UI, displaying a notification, or refreshing data.

In Laravel, the Laravel Echo library is commonly used to handle real-time broadcasting listeners on the client side. This library abstracts away the complexity of managing WebSocket connections and simplifies subscribing to channels and listening for events.

WebSockets

The WebSocket protocol, a full-duplex communication protocol that provides permanent connections between the server and the client, is crucial to real-time broadcasting. Unlike HTTP, which is request-response oriented, WebSockets allow for continuous data flow in both directions, making them ideal for real-time applications where data changes must be delivered to clients instantaneously.

Think of WebSockets as a phone call between the server and the client. Once the call (connection) is made, they can talk back and forth without hanging up. This makes it faster than constantly refreshing the page or checking for updates.

In real-time broadcasting, WebSockets create an open connection between the server and the client. When an event happens, the server can instantly send the update through the web socket to the client, and the client will see the change immediately.

Build a simple real time application

Now that you've learned about the key concepts of real-time broadcasting, it's time to put them into practice. In this section, you'll create a simple to-do application.

The idea is that multiple users can engage with the to-do list simultaneously, and any modifications made by one user are immediately visible to all others in real-time. Let's get started.

Step 1: Create a model and migration

The first thing you need to do is to create the Task model to store information regarding each task. Run the command below to create the Task model class and migration schema:

php artisan make:model Task -m

In the newly created migration file, add fields for the task's name and status. Open the migration file located in the database/migrations directory ending with _create_tasks_table.php and add the update it to match the following:

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('tasks', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->boolean('completed')->default(false);
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('tasks');
    }
};

Then,In the app/Models/Task.php file, add the code below:

protected $guarded = ['id'];
protected $casts = [
    'completed' => 'boolean',
];

Run the migration:

php artisan migrate

Step 2: Create event classes for broadcasting

For the application, only three events would be listened to. When the tasks are added, updated, and deleted. So, create these event classes by running the following commands:

php artisan make:event TaskAdded
php artisan make:event TaskUpdated
php artisan make:event TaskDeleted

Now, open the app/Events/TaskAdded.php file, and you will see a predefined template. Replace it with this:

<?php

namespace App\Events;

use App\Models\Task;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class TaskAdded implements ShouldBroadcastNow
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    /**
     * Create a new event instance.
     */
    public Task $task;

    public function __construct(Task $task)
    {
        $this->task = $task;
    }

    /**
     * Get the channels the event should broadcast on.
     *
     * @return Channel
     */
    public function broadcastOn(): Channel
    {
        return new Channel('tasks');
    }
}

Do the same for app/Events/TaskUpdated.php:

<?php

namespace App\Events;

use App\Models\Task;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class TaskUpdated implements ShouldBroadcastNow
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    /**
     * Create a new event instance.
     */
    public Task $task;

    public function __construct(Task $task)
    {
        $this->task = $task;
    }

    /**
     * Get the channels the event should broadcast on.
     *
     * @return Channel
     */
    public function broadcastOn(): Channel
    {
        return new Channel('tasks');
    }
}

And finally for app/Events/TaskDeleted.php:

<?php

namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class TaskDeleted implements ShouldBroadcastNow
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

    /**
     * Create a new event instance.
     */
    public function __construct(public int $id)
    {
    }

    /**
     * Get the data to broadcast.
     *
     * @return array<string, mixed>
     */
    public function broadcastWith(): array
    {
        return ['id' => $this->id];
    }

    /**
     * Get the channels the event should broadcast on.
     *
     * @return Channel
     */
    public function broadcastOn(): Channel
    {
        return new Channel('tasks');
    }
}

Each of the events implements the ShouldBroadcastNow interface, which would broadcast via the sync driver instead of the default driver allowing them to be dispatched instantaneously.

Step 3: Write the task’s logic

Next, you need to define the logic that would handle the task creation, update, and deletion process. But you must first create a controller to hold this logic, by running the following command:

php artisan make:controller TaskController

Then, update app/Http/Controllers/TaskController.php to match the following:

<?php

namespace App\Http\Controllers;

use App\Events\TaskAdded;
use App\Events\TaskDeleted;
use App\Events\TaskUpdated;
use App\Models\Task;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;

class TaskController extends Controller
{
    public function index()
    {
        return view('tasks.index');
    
    }
    public function all(): JsonResponse
    {
        $tasks = Task::all();
        return response()->json([
            'tasks' => $tasks
        ]);
    }

    public function store(Request $request): Task
    {
        $task = Task::create($request->only('name'));
        broadcast(new TaskAdded($task))->toOthers();
        return $task;
    }
    
    public function update(Request $request, Task $task): Task
    {
        $task->update($request->only('title', 'completed'));
        broadcast(new TaskUpdated($task))->toOthers();
        return $task;
    }

    public function destroy(Task $task): JsonResponse
    {
        $task->delete();
        broadcast(new TaskDeleted($task))->toOthers();
        return response()->json(['status' => 'Task deleted']);
    }
}

Step 4: Define routes

Now, it's time to update the application's routing table. In routes/web.php, add the routes below, along with the accompanying use statements to define routes to handle the creation, update, and deletion of tasks.

<?php

use App\Http\Controllers\TaskController;
use Illuminate\Support\Facades\Route;

Route::get('/', [TaskController::class, 'index']);
Route::get('/tasks/all', [TaskController::class, 'all']);
Route::post('/tasks', [TaskController::class, 'store']);
Route::put('/tasks/{task}', [TaskController::class, 'update']);
Route::delete('/tasks/{task}', [TaskController::class, 'destroy']);

Step 5: Build the frontend

This application's front end will be built with Vue.js. To achieve this, install the vite-vue plugin (which installs Vue.js behind the scenes).

npm install --save-dev @vitejs/plugin-vue

After the installation is complete, open the vite.config.js file in the project root and update it to match the following code.

import {defineConfig} from 'vite';
import laravel from 'laravel-vite-plugin';
import vue from '@vitejs/plugin-vue';

export default defineConfig({
    plugins: [
        laravel({
            input: [
                'resources/css/app.css',
                'resources/js/app.js',
            ],
            refresh: true,
        }),
        vue({
            template: {
                transformAssetUrls: {
                    base: null,
                    includeAbsolute: false,
                },
            },
        }),
    ],
    resolve: {
        alias: {
            vue: 'vue/dist/vue.esm-bundler.js',
        },
    },
});

With Vue.js integrated into the Laravel application, create a directory named components in the resources/js directory. Then, create a component named TasksComponent.vue in the resources/js/components directory.

Open the newly generated file and insert the code shown below into it.

<template>
    <div>
        <h1 class="text-2xl font-bold mb-4 text-center">To-Do App</h1>
        <ul class="space-y-4">
            <li v-for="task in tasks" :key="task.id" class="flex justify-between items-center bg-gray-50 p-4 rounded shadow-sm">
                <div class="flex items-center">
                    <input type="checkbox" v-model="task.completed" @change="updateTask(task)" class="mr-2">
                    <span :class="{'line-through text-gray-500': task.completed}" class="text-lg">{{ task.name }}</span>
                </div>
                <button @click="deleteTask(task)" class="text-red-600 hover:text-red-800 font-semibold">Delete</button>
            </li>
        </ul>
        <div class="mt-6 flex gap-x-6 justify-center items-center ">
            <input
                v-model="newTask"
                @keyup.enter="addTask"
                placeholder="Add a new task"
                class="w-full px-4 py-2 border rounded focus:outline-none focus:ring focus:border-blue-300"
            />
             <button @click="addTask"  class="w-32 rounded-md bg-indigo-600 px-3.5 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500">Add Task</button>
        </div>
    </div>
</template>

<script setup>
import { ref, onMounted } from 'vue';
import axios from 'axios';

// Reactive variables
const tasks = ref([]);
const newTask = ref('');

// Fetch tasks on component mount
const fetchTasks = () => {
    axios.get('/tasks/all').then((response) => {
        tasks.value = response.data.tasks;
    });
};

// Add a new task
const addTask = () => {
    if (newTask.value.trim()) {
        axios.post('/tasks', { name: newTask.value }).then(() => {
            newTask.value = '';
        });
    }
};

// Update task (complete/uncomplete)
const updateTask = (task) => {
    axios.put(`/tasks/${task.id}`, { title: task.title, completed: task.completed });
};

// Delete a task
const deleteTask = (task) => {
    axios.delete(`/tasks/${task.id}`);
};

// Listen for real-time updates
const setupListeners = () => {
    window.Echo.channel('tasks')
        .listen('TaskAdded', (event) => {
            tasks.value.push(event.task);
        })
        .listen('TaskUpdated', (event) => {
            const task = tasks.value.find((t) => t.id === event.task.id);
            if (task) {
                task.completed = event.task.completed;
            }
        })
        .listen('TaskDeleted', (event) => {
            tasks.value = tasks.value.filter((t) => t.id !== event.id);
        });
};

// Initialize on component mount
onMounted(() => {
    fetchTasks();
    setupListeners();
});
</script>

Now, you need to import Vue and register the newly created component and mount it to the #app element, by updating resources/js/app.js to match the code below.

import './bootstrap';
import { createApp } from 'vue';
import TasksComponent from './components/TasksComponent.vue';

createApp({})
    .component('TasksComponent', TasksComponent)
    .mount('#app')

Following that, create a new directory named tasks in resources/views and in that directory create a file named index.blade.php. After that, add the code below into it:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <title>To-Do App</title>
    <!-- Include Tailwind CSS via CDN -->
    <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
</head>
<body class="bg-gray-100 p-10">
<div id="app" class="max-w-2xl mx-auto bg-white p-6 rounded shadow">
    <tasks-component></tasks-component>
</div>
<!-- Include Vite scripts for the app.js file -->
@vite('resources/js/app.js')
</body>
</html>

Test the application

Now that the application has been built, it's time to test it. First though, run the build command below to allow Vite to compile everything.

npm run build

Next, start the Reverb server with a debug option to see the logs of the WebSocket connections from the terminal:

php artisan reverb:start --debug

And in another terminal instance, start the Laravel server.

php artisan serve

After the server has started up, open the URL http://127.0.0.1:8000/ in two browser instances, placing them side by side. Create, complete, or delete tasks in one, and see how they are affected immediately in the other without refreshing the page.

Screenshot of a to-do app with tasks and options to add, complete, or delete tasks.

That's the essentials of real time broadcasting in Laravel

This article covered the basic concepts of real-time broadcasting. We discussed fundamental topics such as channel events, listeners, and web sockets, as well as how they work together to provide rapid updates to users.

Now that you've learned the processes, you're ready to include real-time features into your own Laravel applications and provide dynamic, engaging experiences for your users. Happy building!

The code is available on GitHub.

Prosper is a freelance Laravel web developer and technical writer who enjoys working on innovative projects that use open-source software. When he's not coding, he searches for the ideal startup opportunities to pursue. You can find him on Twitter and LinkedIn.

Broadcasting icons used in the post's main image were created by Freepik on Flaticon.