Adding Real Time Chat to Laravel Using Reverb & Vue

Laravel, the web artisan's favorite PHP framework, has just got a whole new powerful tool in its arsenal: Reverb. Among the official packages of Laravel, this WebSocket server application would seamlessly let you integrate real-time features in your Laravel-based applications, thereby taking interaction to a whole new level.

What is Laravel Reverb?

Reverb acts as a mediator between your Laravel-based application and its users. It establishes two-way, real-time communication based on WebSockets technology that allows web pages to receive updates on the server without a complete page refresh. This means that your users experience your application more dynamically and responsively.

Key Features of Laravel Reverb

Blazing Speed: Provides outstanding performance for real-time information with no lags.

Scalability: Grow with your applications to handle increased user traffic.

Seamless Integration: It works with broadcasting features added to Laravel and Laravel Echo to make development simple.

Push Updates: Push updates, messages, or events to clients to share your information instantly.

Built-in Security: Data encryption and authentication assurance for security communication

Adding Laravel Reverb to Your Chat Project

With Laravel Reverb, you can build dynamic chat applications. The messages are posted instantly, making users involved comprehensively. Here's a breakdown of the steps involved:

Step 1: Setting Up Your Laravel Project:

  • Ensure you have a Laravel application set up (version 11 or above is recommended).

  • If you're starting fresh, use composer create-project laravel/laravel your-chat-app-name.

Step 2: Install and Configure Reverb:

Install Laravel Reverb by running the following command:

php artisan install:broadcasting

Once you’ve installed Reverb, you can now modify its configuration from the config/reverb.php file. To establish a connection to Reverb, a set of Reverb “application” credentials must be exchanged between the client and server. These credentials are configured on the server and are used to verify the request from the client. You can define these credentials using the following environment variables:

BROADCAST_DRIVER=reverb
REVERB_APP_ID=my-app-id
REVERB_APP_KEY=my-app-key
REVERB_APP_SECRET=my-app-secret

It also automatically creates echo.js in the resources/js directory.

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'],
});

Follow the Laravel documentation for configuration steps specific to your application server https://laravel.com/docs/11.x/reverb

Step 3: Running a Server

You can launch the Reverb server by using the reverb:start Artisan command:

php artisan reverb:start

By default, the Reverb server will be started at 0.0.0.0:8080, which makes it accessible from all network interfaces.

If you want to set a specific host or port, you can use the –host and –port options when starting the server.

php artisan reverb:start --host=127.0.0.1 --port=9000

You can also define REVERB_SERVER_HOST and REVERB_SERVER_PORT environment variables in your application’s .env configuration file.

Step 4: Setup Database

Open your .env file and adjust the settings to set up your database. Here’s an example using SQLite for simplicity:

DB_CONNECTION=sqlite
DB_DATABASE=/path/to/database.sqlite

You can create an SQLite database by simply running:

touch /path/to/database.sqlite

For this demo, we’ll create five predefined rooms. Let’s start by generating a model ChatMessage with migration for a chat_messages table.

php artisan make:model ChatMessage --migration

To make it simpler, only create name attributes for this model and migrate it.

 Schema::create('chat_messages', function (Blueprint $table) {
         $table->id();
         $table->foreignId('receiver_id');
         $table->foreignId('sender_id');
         $table->text('text');
         $table->timestamps();
});
php artisan migrate

Now, let's add the necessary relationships in the ChatMessage model. Open the ChatMessage.php file in the app/Models directory and update it as follows:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class ChatMessage extends Model
{
    use HasFactory;

    protected $fillable = [
        'sender_id',
        'receiver_id',
        'text'
    ];

    public function sender()
    {
        return $this->belongsTo(User::class, 'sender_id');
    }

    public function receiver()
    {
        return $this->belongsTo(User::class, 'receiver_id');
    }
}

Step 5: Create Event

To handle the broadcasting of messages, we'll create an event called MessageSent. This event will implement Laravel's ShouldBroadcastNow interface, which allows for immediate broadcasting over WebSockets without queuing. Follow these steps to create and set up the event:

  1. Create a new PHP file in the App\Events directory and name it MessageSent.php.

  2. Open the newly created file and add the following code:

<?php

namespace App\Events;

use App\Models\ChatMessage;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class MessageSent implements ShouldBroadcastNow
{
    use Dispatchable;
    use InteractsWithSockets;
    use SerializesModels;

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

    /**
     * Get the channels the event should broadcast on.
     *
     * @return array<int, \Illuminate\Broadcasting\Channel>
     */
    public function broadcastOn(): array
    {
        return [
            new PrivateChannel("chat.{$this->message->receiver_id}"),
        ];
    }
}

Step 6: Create a Private Channel Route

The channels.php file in Laravel applications plays a crucial role in defining broadcast channels used for real-time communication features.

<?php

use Illuminate\Support\Facades\Broadcast;

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

This code defines a private channel named chat.{id} using Laravel's Broadcast facade. Private channels restrict access based on user authentication and authorization logic.

Step 7: Defining Routes

1. Chat Room Route:

Route::get('/chat/{friend}', function (User $friend) {
    return view('chat', [
        'friend' => $friend
    ]);
})->middleware(['auth'])->name('chat');

This route is responsible for rendering the chat interface. It accepts a dynamic parameter {friend} representing the user's chat partner.

3. Get Chat Messages Route:

Route::get('/messages/{friend}', function (User $friend) {
    return ChatMessage::query()
        ->where(function ($query) use ($friend) {
            $query->where('sender_id', auth()->id())
                ->where('receiver_id', $friend->id);
        })
        ->orWhere(function ($query) use ($friend) {
            $query->where('sender_id', $friend->id)
                ->where('receiver_id', auth()->id());
        })
        ->with(['sender', 'receiver'])
        ->orderBy('id', 'asc')
        ->get();
})->middleware(['auth']);

This route retrieves chat messages exchanged between the authenticated user and the specified friend ({friend}). The query ensures it retrieves messages where either the user is the sender or receiver, including both directions of the conversation.

4. Send Chat Message Route:

Route::post('/messages/{friend}', function (User $friend) {
    $message = ChatMessage::create([
        'sender_id' => auth()->id(),
        'receiver_id' => $friend->id,
        'text' => request()->input('message')
    ]);

    broadcast(new MessageSent($message));

    return $message;
});

After creating the message, it leverages Laravel's broadcasting functionality using broadcast(new MessageSent($message)). This line broadcasts the newly created message to all connected users through Reverb, enabling real-time chat functionality.

Step 8: Creating the Blade View

To render the chat interface, you'll need to create a Blade view file. Create a new file named chat.blade.php in the resources/views directory and add the following code:

<x-app-layout>
    <x-slot name="header">
        <h2 class="text-xl font-semibold leading-tight text-gray-800">
            {{ $friend->name }}
        </h2>
    </x-slot>

    <div class="py-12">
        <div class="mx-auto max-w-7xl sm:px-6 lg:px-8">
            <div class="overflow-hidden bg-white shadow-sm sm:rounded-lg">
                <div class="p-6 bg-white border-b border-gray-200">
                    <chat-component 
                       :friend="{{ $friend }}" 
                       :current-user="{{ auth()->user() }}" 
                    />
                </div>
            </div>
        </div>
    </div>
</x-app-layout>

The key element here is <chat-component :friend="{{ $friend }}" :current-user="{{ auth()->user() }}" />. This line renders a Vue.js component named chat-component.

Step 8: Create a Chat Component

<template>
    <div>
        <div class="flex flex-col justify-end h-80">
            <div ref="messagesContainer" class="p-4 overflow-y-auto max-h-fit">
                <div
                    v-for="message in messages"
                    :key="message.id"
                    class="flex items-center mb-2"
                >
                    <div
                        v-if="message.sender_id === currentUser.id"
                        class="p-2 ml-auto text-white bg-blue-500 rounded-lg"
                    >
                        {{ message.text }}
                    </div>
                    <div v-else class="p-2 mr-auto bg-gray-200 rounded-lg">
                        {{ message.text }}
                    </div>
                </div>
            </div>
        </div>
        <div class="flex items-center">
            <input
                type="text"
                v-model="newMessage"
                @keydown="sendTypingEvent"
                @keyup.enter="sendMessage"
                placeholder="Type a message..."
                class="flex-1 px-2 py-1 border rounded-lg"
            />
            <button
                @click="sendMessage"
                class="px-4 py-1 ml-2 text-white bg-blue-500 rounded-lg"
            >
                Send
            </button>
        </div>
        <small v-if="isFriendTyping" class="text-gray-700">
            {{ friend.name }} is typing...
        </small>
    </div>
</template>

<script setup>
import axios from "axios";
import { nextTick, onMounted, ref, watch } from "vue";

const props = defineProps({
    friend: {
        type: Object,
        required: true,
    },
    currentUser: {
        type: Object,
        required: true,
    },
});

const messages = ref([]);
const newMessage = ref("");
const messagesContainer = ref(null);
const isFriendTyping = ref(false);
const isFriendTypingTimer = ref(null);

watch(
    messages,
    () => {
        nextTick(() => {
            messagesContainer.value.scrollTo({
                top: messagesContainer.value.scrollHeight,
                behavior: "smooth",
            });
        });
    },
    { deep: true }
);

const sendMessage = () => {
    if (newMessage.value.trim() !== "") {
        axios
            .post(`/messages/${props.friend.id}`, {
                message: newMessage.value,
            })
            .then((response) => {
                messages.value.push(response.data);
                newMessage.value = "";
            });
    }
};

const sendTypingEvent = () => {
    Echo.private(`chat.${props.friend.id}`).whisper("typing", {
        userID: props.currentUser.id,
    });
};

onMounted(() => {
    axios.get(`/messages/${props.friend.id}`).then((response) => {
        console.log(response.data);
        messages.value = response.data;
    });

    Echo.private(`chat.${props.currentUser.id}`)
        .listen("MessageSent", (response) => {
            messages.value.push(response.message);
        })
        .listenForWhisper("typing", (response) => {
            isFriendTyping.value = response.userID === props.friend.id;

            if (isFriendTypingTimer.value) {
                clearTimeout(isFriendTypingTimer.value);
            }

            isFriendTypingTimer.value = setTimeout(() => {
                isFriendTyping.value = false;
            }, 1000);
        });
});
</script>

This Vue.js component manages the chat interface's dynamic behaviour. It displays a scrollable message list, styled differently based on the sender (current user or friend). It provides an input field for composing new messages and a button to send them. It leverages axios for making HTTP requests to fetch initial messages and send new ones.

Real-time functionality is achieved using Laravel Echo:

  1. It listens for broadcasted MessageSent events to update the message list whenever a new message arrives.

  2. It utilizes whispers on private channels to notify the chat partner about the user's typing activity and receive similar notifications from the friend.

Step 9: Run Project

To run the Laravel project, we need to execute the following command:

php artisan serve

For starting front:

npm run dev

Run reverb:

php artisan reverb:start

Source Code

You can find the source code for this Laravel Reverb chat implementation in the following GitHub repository: https://github.com/qirolab/laravel-reverb-chat

By utilizing Laravel Reverb, developers can ensure their applications remain responsive and dynamic, providing real-time updates and interactions that modern users expect. Happy coding!


The post Adding Real Time Chat to Laravel Using Reverb & Vue appeared first on Laravel News.

Join the Laravel Newsletter to get all the latest Laravel articles like this directly in your inbox.

Read more

© 2024 Extly, CB - All rights reserved.