DEV Community

Cover image for Implementing Real-Time Notifications in Laravel with Laravel Reverb and Pusher
Muhammmad Nawaz
Muhammmad Nawaz

Posted on • Edited on • Originally published at invidiatech.com

Implementing Real-Time Notifications in Laravel with Laravel Reverb and Pusher

Real-time notifications enhance user experience by delivering instant updates without requiring page refreshes. In this article, we'll walk through the complete process of implementing real-time notifications in a Laravel 11 application using Laravel Reverb (a first-party WebSocket server for Laravel) and Pusher (a cloud-based WebSocket service). We'll create a simple application where an admin user receives real-time notifications when a new post is created by a user. This guide assumes you have a basic understanding of Laravel, PHP, and JavaScript, and have Node.js, Composer, and a database (e.g., MySQL or SQLite) installed.
cd real-time-notifications

  • Set up the database: For simplicity, we'll use SQLite as the database. Create a database.sqlite file in the database directory:touch database/database.sqlite
  • Update the .env file to use SQLite:DB_CONNECTION=sqlite DB_DATABASE=/absolute/path/to/real-time-notifications/database/database.sqlite
  • Replace /absolute/path/to/ with the actual path to your project directory.- Install Laravel Breeze: Laravel Breeze provides a simple authentication scaffold. Install it with:composer require laravel/breeze --dev php artisan breeze:install
  • Choose the following options during installation:- Blade for the stack- Yes for SSR (Server-Side Rendering, optional)- Yes for Reverb broadcasting- SQLite as the database- Run the migrations to set up the users table:php artisan migrate
  • Add an admin column: Modify the users table to include an is_admin column to distinguish admin users. Create a migration:php artisan make:migration add_is_admin_to_users_table
  • Edit the generated migration file (database/migrations/YYYY_MM_DD_add_is_admin_to_users_table.php):<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->boolean('is_admin')->default(false);
});
}

public function down(): void
{
    Schema::table('users', function (Blueprint $table) {
        $table-&gt;dropColumn('is_admin');
    });
}
Enter fullscreen mode Exit fullscreen mode

};

  • Run the migration:php artisan migrate
  • Seed an admin user: Modify the database/seeders/DatabaseSeeder.php to create an admin user:<?php namespace Database\Seeders;

use Illuminate\Database\Seeder;
use App\Models\User;

class DatabaseSeeder extends Seeder
{
public function run(): void
{
User::create([
'name' => 'Admin User',
'email' => 'admin@example.com',
'password' => bcrypt('password'),
'is_admin' => true,
]);

    User::factory(5)-&gt;create(); // Create 5 regular users
}
Enter fullscreen mode Exit fullscreen mode

}

  • Run the seeder:php artisan db:seed ## Step 2: Set Up Laravel Reverb and Pusher- Install Laravel Reverb: If you selected Reverb during the Breeze installation, it’s already installed. If not, run:php artisan install:broadcasting
  • Choose Reverb as the broadcasting driver. This command installs laravel-echo and pusher-js and creates the config/broadcasting.php and resources/js/echo.js files.- Configure Pusher: Sign up for a free Pusher account at pusher.com. Create a new Channels app and note down the credentials (App ID, Key, Secret, and Cluster).- Update the .env file with Pusher credentials:BROADCAST_CONNECTION=pusher PUSHER_APP_ID=your-app-id PUSHER_APP_KEY=your-app-key PUSHER_APP_SECRET=your-app-secret PUSHER_APP_CLUSTER=your-app-cluster
  • For Reverb, add the following to your .env file:REVERB_APP_ID=256980 REVERB_APP_KEY=f4l2tmwqf6eg0f6jz0mw REVERB_APP_SECRET=zioqeto9xrytlnlg7sj6 REVERB_HOST="localhost" REVERB_PORT=8080 REVERB_SCHEME=http VITE_REVERB_APP_KEY="${REVERB_APP_KEY}" VITE_REVERB_HOST="${REVERB_HOST}" VITE_REVERB_PORT="${REVERB_PORT}" VITE_REVERB_SCHEME="${REVERB_SCHEME}"
  • These are example credentials for Reverb. For production, generate unique credentials or use Pusher directly.- Configure Laravel Echo: The install:broadcasting command creates resources/js/echo.js. Update it to use Pusher: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,
forceTLS: true,
});

  • Alternatively, if using Reverb, configure it as follows:import Echo from 'laravel-echo'; import Pusher from 'pusher-js';

window.Pusher = Pusher;

window.Echo = new Echo({
broadcaster: 'reverb',
key: import.meta.env.VITE_REVERB_APP_KEY,
wsHost: import.meta.env.VITE_REVERB_HOST,
wsPort: import.meta.env.VITE_REVERB_PORT ?? 80,
wssPort: import.meta.env.VITE_REVERB_PORT ?? 443,
forceTLS: (import.meta.env.VITE_REVERB_SCHEME ?? 'https') === 'https',
enabledTransports: ['ws', 'wss'],
});

  • Compile front-end assets: Install Node.js dependencies and compile assets:npm install npm run dev ## Step 3: Create the Post Model and Event- Create the Post model: Create a Post model with a migration and controller:php artisan make:model Post -mcr
  • Edit the migration file (database/migrations/YYYY_MM_DD_create_posts_table.php):<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
public function up(): void
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('body');
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->timestamps();
});
}

public function down(): void
{
    Schema::dropIfExists('posts');
}
Enter fullscreen mode Exit fullscreen mode

};

  • Run the migration:php artisan migrate
  • Create a PostCreated event: Create an event to broadcast when a post is created:php artisan make:event PostCreated
  • Edit app/Events/PostCreated.php:<?php namespace App\Events;

use App\Models\Post;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class PostCreated
{
use Dispatchable, InteractsWithSockets, SerializesModels;

public $post;

public function __construct(Post $post)
{
    $this-&gt;post = $post;
}

public function broadcastOn(): array
{
    return [
        new PrivateChannel('admin-notifications'),
    ];
}

public function broadcastAs(): string
{
    return 'post.created';
}
Enter fullscreen mode Exit fullscreen mode

}

  • Authorize the channel: Update routes/channels.php to authorize the admin channel:<?php use App\Models\User; use Illuminate\Support\Facades\Broadcast;

Broadcast::channel('admin-notifications', function (User $user) {
return $user->is_admin;
});

Step 4: Create a Notification- Create a PostCreatedNotification: Generate a notification class:php artisan make:notification PostCreatedNotification

  • Edit app/Notifications/PostCreatedNotification.php:<?php namespace App\Notifications;

use App\Models\Post;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\BroadcastMessage;
use Illuminate\Notifications\Notification;

class PostCreatedNotification extends Notification
{
use Queueable;

public $post;

public function __construct(Post $post)
{
    $this-&gt;post
Enter fullscreen mode Exit fullscreen mode

= $post; }

public function via(object $notifiable): array
{
return ['broadcast'];
}

public function toBroadcast(object $notifiable): BroadcastMessage
{
return new BroadcastMessage([
'message' => "New post created: {$this->post->title} by {$this->post->user->name}",
'post_id' => $this->post->id,
]);
}
}


Step 5: Set Up the Post Controller and Routes

  1. Update the PostController: Edit app/Http/Controllers/PostController.php to handle post creation and dispatch the event:

php
&lt;?php
namespace App\Http\Controllers;

use App\Events\PostCreated;
use App\Models\Post;
use App\Models\User;
use App\Notifications\PostCreatedNotification;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Notification;

class PostController extends Controller
{
    public function index()
    {
        return view('posts.index', ['posts' =&gt; Post::all()]);
    }

    public function create()
    {
        return view('posts.create');
    }

    public function store(Request $request)
    {
        $request-&gt;validate([
            'title' =&gt; 'required|string|max:255',
            'body' =&gt; 'required|string',
        ]);

        $post = Post::create([
            'title' =&gt; $request-&gt;title,
            'body' =&gt; $request-&gt;body,
            'user_id' =&gt; auth()-&gt;id(),
        ]);

        // Dispatch event
        event(new PostCreated($post));

        // Notify admins
        $admins = User::where('is_admin', true)-&gt;get();
        Notification::send($admins, new PostCreatedNotification($post));

        return redirect()-&gt;route('posts.index')-&gt;with('success', 'Post created successfully!');
    }
}
- **Define routes**: Update routes/web.php:&lt;?php
use App\Http\Controllers\PostController;
use Illuminate\Support\Facades\Route;

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

Auth::routes();

Route::get('/posts', [PostController::class, 'index'])-&gt;name('posts.index');
Route::get('/posts/create', [PostController::class, 'create'])-&gt;name('posts.create');
Route::post('/posts', [PostController::class, 'store'])-&gt;name('posts.store');
## Step 6: Create Views- **Create the posts index view**: Create resources/views/posts/index.blade.php:@extends('layouts.app')

@section('content')
    &lt;div class="container"&gt;
        &lt;h1&gt;Posts&lt;/h1&gt;
        &lt;a href="{{ route('posts.create') }}" class="btn btn-primary mb-3"&gt;Create Post&lt;/a&gt;
        &lt;div id="notifications" class="alert alert-info" style="display: none;"&gt;&lt;/div&gt;
        &lt;ul class="list-group"&gt;
            @foreach ($posts as $post)
                &lt;li class="list-group-item"&gt;{{ $post-&gt;title }} by {{ $post-&gt;user-&gt;name }}&lt;/li&gt;
            @endforeach
        &lt;/ul&gt;
    &lt;/div&gt;
@endsection

@section('script')
    &lt;script&gt;
        Echo.private('admin-notifications')
            .listen('.post.created', (e) =&gt; {
                const notificationDiv = document.getElementById('notifications');
                notificationDiv.style.display = 'block';
                notificationDiv.innerText = e.message;
                setTimeout(() =&gt; {
                    notificationDiv.style.display = 'none';
                }, 5000);
            });
    &lt;/script&gt;
@endsection
- **Create the post creation view**: Create resources/views/posts/create.blade.php:@extends('layouts.app')

@section('content')
    &lt;div class="container"&gt;
        &lt;h1&gt;Create Post&lt;/h1&gt;
        &lt;form method="POST" action="{{ route('posts.store') }}"&gt;
            @csrf
            &lt;div class="mb-3"&gt;
                &lt;label for="title" class="form-label"&gt;Title&lt;/label&gt;
                &lt;input type="text" class="form-control" id="title" name="title" required&gt;
            &lt;/div&gt;
            &lt;div class="mb-3"&gt;
                &lt;label for="body" class="form-label"&gt;Body&lt;/label&gt;
                &lt;textarea class="form-control" id="body" name="body" required&gt;&lt;/textarea&gt;
            &lt;/div&gt;
            &lt;button type="submit" class="btn btn-primary"&gt;Submit&lt;/button&gt;
        &lt;/form&gt;
    &lt;/div&gt;
@endsection
- **Update the main layout**: Ensure resources/views/layouts/app.blade.php includes the necessary scripts. The Breeze installation should have set this up, but verify that @vite(['resources/sass/app.scss', 'resources/js/app.js']) is included in the &lt;head&gt; section.## Step 7: Start the Servers- **Start the Laravel development server**:php artisan serve
- **Start the Reverb server** (if using Reverb):php artisan reverb:start
- If using Pusher, you don’t need to run the Reverb server, as Pusher handles WebSocket connections.- **Compile front-end assets**:npm run dev
- **Start the queue worker**: Broadcasting requires a queue worker to process events:php artisan queue:work
## Step 8: Test the Application- **Register a normal user**: Visit http://localhost:8000/register and create a regular user account.- **Log in as the admin**: Log in with admin@example.com and password.- **Create a post**: Log in as a regular user, navigate to /posts/create, and create a new post.- **Check the notification**: As the admin user, visit /posts. When a new post is created by any user, you should see a notification appear in real-time on the page (e.g., "New post created: [Title] by [User]").## Step 9: Optional Enhancements- **Styling**: Use Tailwind CSS (included with Breeze) to improve the UI of the notification and post forms.- **Multiple Channels**: Extend the application to support multiple notification types or channels (e.g., public channels for all users).- **Queue Optimization**: Configure a more robust queue driver (e.g., Redis) for production.- **Security**: Ensure proper authentication and authorization for private channels in production.- **Novu Integration**: For a more comprehensive notification system, consider integrating Novu for multi-channel notifications (e.g., email, SMS) alongside real-time WebSocket notifications.## Troubleshooting- **No notifications received**: Ensure the Reverb server or Pusher is running, and check the browser console for WebSocket errors.- **Channel authorization issues**: Verify the channels.php configuration and ensure the user is authorized for the admin-notifications channel.- **Queue issues**: Ensure the queue worker is running (php artisan queue:work) and the QUEUE_CONNECTION in .env is set to a valid driver (e.g., database or redis).## ConclusionIn this article, we implemented a real-time notification system in Laravel 11 using Laravel Reverb and Pusher. We created a simple application where admins receive instant notifications when users create posts. This setup leverages Laravel’s broadcasting system, Laravel Echo, and WebSocket technology to deliver a seamless user experience.

For further reading, check the official documentation:

- Laravel Reverb- Laravel Broadcasting- Pusher Channels
Enter fullscreen mode Exit fullscreen mode

Top comments (0)