Laravel & Vue: Real-Time Notifications with Reverb, Redis, and TDD - 02

Chapter 2: Creating Notification Channels with Reverb in Laravel

Objective

Implement secure and customized notification channels in Laravel using Reverb, starting with failing tests and following the TDD cycle of red → green → refactor. This includes:

1. Creating channels to transmit notifications to individual users or administrators.
2. Securing the channels with permissions and restrictions.
3. Validating functionality through error-guided solutions and tests.

Introduction

This chapter uses TDD to implement custom notification channels in Laravel with Reverb. We will first write tests that will fail, identifying what the application lacks, and based on those errors, we will develop the necessary code.

We will focus on two types of channels:

  • Private channels for users.
  • A global channel for administrators.

For now, we will use the User model with the is_admin attribute to simplify role differentiation.

TDD Cycle for User Channels

Write Tests for Private User Channels

Create a test file:

sail artisan make:test UserNotificationChannelTest

Modify tests/Feature/UserNotificationChannelTest.php:

namespace Tests\Feature;

use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class UserNotificationChannelTest extends TestCase
{
    use RefreshDatabase;

    public function test_user_can_access_own_notification_channel()
    {
        // Given
        $user = User::factory()->create();

        // When
        $response = $this->actingAs($user)->post('/broadcasting/auth', [
            'channel_name' => 'private-user-notifications.' . $user->id,
            'socket_id' => '12345.67890', // valid socket_id format
        ], ['X-CSRF-TOKEN' => csrf_token()]);

        // Then
        $response->assertStatus(200);
    }

    public function test_user_cannot_access_other_users_notification_channel()
    {
        // Given
        $user = User::factory()->create();
        $otherUser = User::factory()->create();

        // When
        $response = $this->actingAs($user)->post('/broadcasting/auth', [
            'channel_name' => 'private-user-notifications.' . $otherUser->id,
            'socket_id' => '12345.67890', // valid socket_id format
        ], ['X-CSRF-TOKEN' => csrf_token()]);

        // Then
        $response->assertStatus(403);
    }
}

Note: The socket_id is a unique identifier generated by the client (typically during a websocket connection) and must follow a specific format expected by Pusher. A valid socket_id should be in the digits.dot.digits format, like 1234.5678. When using Pusher during tests, providing a valid socket_id is essential to avoid errors and allow proper channel authorization.

Run Initial Tests (Red)

Run the tests:

sail artisan test --filter=UserNotificationChannelTest

Expected Errors:

  • 404 Error: The /broadcasting/auth route is not defined.

Error Analysis:
The 404 error indicates that the route for channel authentication is missing. This error guides us to implement the basic broadcasting configuration.

Configure Channel Authentication

1. Create the Broadcast Service Provider
sail artisan make:provider BroadcastServiceProvider
2. Enable the BroadcastServiceProvider:

Ensure the service is registered in bootstrap/providers.php:

<?php

return [
    App\Providers\AppServiceProvider::class,
    //...
    App\Providers\BroadcastServiceProvider::class,
];
3. Define the Broadcasting Route:

Modify the boot method of BroadcastServiceProvider:

namespace App\Providers;

use Illuminate\Support\Facades\Broadcast;
use Illuminate\Foundation\Support\Providers\BroadcastServiceProvider as ServiceProvider;

class BroadcastServiceProvider extends ServiceProvider
{
   public function boot()
   {
       Broadcast::routes(); // Register the '/broadcasting/auth' route

       require base_path('routes/channels.php'); // Define the channels
   }
}
4. Clear the Cache:

After modifying configuration files, it is important to clear Laravel’s cache to apply the changes:

sail artisan config:clear
5. Start the Reverb Server:

Start the server:

sail artisan reverb:start --host="0.0.0.0" --port=8080 --hostname="localhost"

Run Tests for Private User Channels (Red)

Run the tests again. They will now fail with a 403 error, as the channels are not yet defined:

sail artisan test --filter=UserNotificationChannelTest

Implement Private Channels (Green)

Define the channels in routes/channels.php:

Broadcast::channel('user-notifications.{id}', function ($user, $id) {
    return (int) $user->id === (int) $id;
});

Run the tests again. Both should pass:

sail artisan test --filter=UserNotificationChannelTest

Refactor

Review the organization of the routes and channel code. If more channels are added in the future, consider creating a dedicated file for them.

Repeat the TDD Cycle for Administrator Channels

Write Tests for the Administrator Channel

Create a test file:

sail artisan make:test AdminNotificationChannelTest

Modify tests/Feature/AdminNotificationChannelTest.php:

namespace Tests\Feature;

use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class AdminNotificationChannelTest extends TestCase
{
    use RefreshDatabase;

    public function test_admin_can_access_admin_notification_channel()
    {
        // Given
        $admin = User::factory()->state(['is_admin' => true])->create();

        // When
        $response = $this->actingAs($admin)->post('/broadcasting/auth', [
            'channel_name' => 'admin-notifications',
            'socket_id' => '12345.67890', // valid socket_id format
        ], ['X-CSRF-TOKEN' => csrf_token()]);

        // Then
        $response->assertStatus(200);
    }

    public function test_regular_user_cannot_access_admin_notification_channel()
    {
        // Given
        $user = User::factory()->state(['is_admin' => false])->create();

        // When
        $response = $this->actingAs($user)->post('/broadcasting/auth', [
            'channel_name' => 'admin-notifications',
            'socket_id' => '12345.67890', // valid socket_id format
        ], ['X-CSRF-TOKEN' => csrf_token()]);

        // Then
        $response->assertStatus(403);
    }
}

Run Tests for Administrators (Red)

Run the tests. Both will fail, as the administrator channel is not defined:

sail artisan test --filter=AdminNotificationChannelTest

Implement the Administrator Channel

Add the channel in routes/channels.php:

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

Run Tests for Administrators (Red)

Run the tests. Both will fail, as the is_admin attribute does not exist in the User model:

sail artisan test --filter=AdminNotificationChannelTest

Add the missing is_admin Attribute to the User Model

1. Create the Migration

Run the following command to create a new migration:

sail artisan make:migration add_is_admin_to_users_table --table=users
2. Modify the Migration

Open the created migration in database/migrations/ and add the is_admin field:

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

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

    public function down()
    {
        Schema::table('users', function (Blueprint $table) {
            $table->dropColumn('is_admin');
        });
    }
};
3. Update the User Model

Update the User model (app/Models/User.php) to cast is_admin as a boolean:

class User extends Authenticatable
{
    protected $casts = [
        'email_verified_at' => 'datetime',
        'password' => 'hashed',
        'is_admin' => 'boolean',
    ];
}
4. Run the Migration

Run the migration to apply the changes:

sail artisan migrate

Run Tests for Administrators (Green)

sail artisan test --filter=AdminNotificationChannelTest

Now run all tests to verify they all pass:

sail artisan test

Refactor (If Necessary)

Review the routes/channels.php file. If more channels are added, consider grouping them into a dedicated file to improve organization.

Conclusion

In this chapter, we implemented secure channels for users and administrators by following the TDD cycle. The 404 and 403 errors guided us in developing the routes and permissions needed for each channel.

In the next chapter, we will configure Redis to store and manage notifications, optimizing performance and handling unread messages. 🚀

José Rafael Gutierrez

Soy un desarrollador web con más de 14 años de experiencia, especializado en la creación de sistemas a medida. Apasionado por la tecnología, la ciencia, y la lectura, disfruto resolviendo problemas de...

Subscribe for Updates

Provide your email to get email notifications about new posts or updates.