Building Real-Time Chat Applications with Laravel Reverb and Vue 3

Harish Kumar · · 3179 Views

Real-time chat applications are increasingly important for seamless communication and instant connectivity. Laravel Reverb and Vue.js 3 provide developers with the tools to create efficient and feature-rich chat applications.

👉 Don't left behind, get the Spec Coder VSCode Extension now 🔥

Understanding Laravel Reverb: The Key to Real-Time Communication

Laravel Reverb, introduced in Laravel 11, has changed how developers handle real-time communication in their web applications. This built-in WebSocket server, fully integrated into the Laravel ecosystem, allows developers to create two-way, real-time channels between clients and servers, enabling instant data exchange and updates.

The Power of WebSockets

At the heart of Reverb's functionality is the WebSocket protocol, a technology that enables continuous, two-way communication over a single TCP connection. Unlike traditional HTTP requests, which need constant polling or long-polling techniques, WebSockets allow for easy, real-time data transfer in both directions. This greatly reduces latency and improves overall application performance.

Reverb's Unique Advantages

Reverb stands out from traditional third-party solutions because it is self-hosted, giving developers full control over their real-time infrastructure. As a first-party Laravel package, Reverb integrates seamlessly with the framework's existing event broadcasting system, providing a consistent and familiar development experience.

One of Reverb's most impressive features is its speed and scalability. Written in PHP, the package is optimized for fast communication, efficiently handling thousands of concurrent connections. Additionally, its horizontal scalability allows for easy distribution of connections and data across multiple servers, ensuring your application can grow and adapt to increasing demands.

Harnessing the Power of Vue.js for Real-Time Interactivity

While Laravel Reverb provides the foundation for real-time communication, Vue.js, a progressive JavaScript framework, offers a strong and easy-to-use solution for building dynamic, reactive user interfaces. With its lightweight and modular design, Vue.js allows developers to create highly interactive and responsive applications, making it perfect for real-time chat applications.

Real-Time Updates with Vue.js

One of the main benefits of using Vue.js with Laravel Reverb is its ability to handle real-time updates smoothly. By using Vue.js's reactive data binding and virtual DOM features, developers can easily update the user interface in response to real-time events, like incoming messages or user status changes, without needing to manually manipulate the DOM or refresh the page.

Setting the Stage: Installing Laravel and Laravel Reverb

Before delving into the intricacies of building a real-time chat application, it's essential to set up the development environment. This process involves installing Laravel 11 and the Laravel Reverb package, ensuring a solid foundation for your project.

Installing Laravel 11

Laravel 11 can be installed using Composer, the dependency manager for PHP. Open your terminal or command prompt and execute the following command:

composer global require laravel/installer

laravel new laravel-reverb-chat

This command will create a new Laravel 11 project named laravel-reverb-chat in your current directory.

Integrating Laravel Reverb

With Laravel installed, you can proceed to integrate the Laravel Reverb package. This can be accomplished using the install:broadcasting Artisan command:

php artisan install:broadcasting

This command will install and configure the necessary components for Reverb, ensuring seamless integration with your Laravel 11 application.

Crafting the Frontend: Tailwind CSS and Vue 3

To create an engaging and visually appealing chat application, it's important to utilize modern front-end technologies. In this guide, we'll look at how to integrate Tailwind CSS, a utility-first CSS framework, and Vue 3.

There are several packages available for this purpose, such as Laravel-Themer and Laravel Breeze. You can choose any of these options, or alternatively, install Tailwind CSS and Vue 3 manually.

For the purpose of this tutorial, we'll use the Laravel-Themer package, which will automatically set up Tailwind CSS and Vue.js for us, and also supports multi-theming for Laravel applications.

To install the Laravel-Themer package, open the terminal and execute the following command:

composer require qirolab/laravel-themer

Next, to create a theme, run the following command in the terminal and follow the instructions:

php artisan make:theme

You will need to make a few additional configurations, which you can find in the documentation of the Laravel-Themer package. It will also add the login and register features.

Orchestrating the Backend: Models, Controllers, Routes, and Blade Views

With the development environment ready and the frontend technologies in place, it's time to focus on the backend structure of your real-time chat application. This section will guide you through creating controllers, defining routes, and crafting Blade views to manage user interactions and data flow.

Creating the ChatMessage Model & Migration

To start building the backend for our real-time chat application, we need to create a model to represent the chat messages in our database. This model will help us interact with the messages table, making it easier to store, retrieve, and manage chat data.

First, let's generate the ChatMessage model using the Artisan command-line tool. Open your terminal and run the following command:

php artisan make:model ChatMessage -m

This command will create a new model file named ChatMessage.php in the app/Models directory and a migration file in the database/migrations directory. The -m flag ensures that a migration file is created along with the model, which we will use to define the structure of the messages table.

Next, open the migration file that was created. It will be located in the database/migrations directory and will have a name similar to 2023_10_10_000000_create_chat_messages_table.php. Update the up method to define the columns for the messages table:

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

After defining the table structure, run the migration to create the table in your database:

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

With the ChatMessage model and its corresponding table in place, we are now ready to move on to creating controllers, defining routes, and crafting Blade views to handle user interactions and data flow in our real-time chat application.

Defining Routes:

Laravel applications rely on routes to define how incoming HTTP requests are handled. In our real-time chat application built with Reverb and Vue.js 3, we'll define several routes to manage user interactions and message flow. All the routes we define here utilize the auth middleware, ensuring only authenticated users can access these functionalities.

1. Dashboard Route:

Route::get('/dashboard', function () {
    return view('dashboard', [
        'users' => User::where('id', '!=', auth()->id())->get()
    ]);
})->middleware(['auth'])->name('dashboard');

This route handles requests to the /dashboard URI. The closure function retrieves a list of users excluding the currently authenticated user using User::where('id', '!=', auth()->id())->get() and then renders the dashboard view with this user list. The middleware property applies the auth middleware, restricting access to authenticated users only. Additionally, the route is named dashboard for convenient reference elsewhere in the application.

2. 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. Laravel automatically injects a User instance based on this parameter using route model binding. The closure retrieves the specified user and passes it to the chat view for display. Similar to the dashboard route, auth middleware and route naming conventions are applied.

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}). It utilizes Laravel's query builder to fetch messages from the ChatMessage model. The query ensures it retrieves messages where either the user is the sender or receiver, including both directions of the conversation. Additionally, it utilizes eager loading (with) to fetch related sender and receiver user information for each message. Finally, messages are ordered chronologically using orderBy('id', 'asc').

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;
});

This route handles POST requests to send a new chat message. It accepts the {friend} parameter similar to the previous route. The route creates a new ChatMessage instance with the sender being the authenticated user, the receiver being the specified friend, and the message content retrieved from the request body using request()->input('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. Finally, the newly created message is returned as the response.

By defining these routes, we establish a communication channel between users and enable real-time message exchange within our Laravel chat application.

Creating the MessageSent 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}"),
        ];
    }
}

This event class uses several traits provided by Laravel to handle the broadcasting process efficiently. The Dispatchable trait allows the event to be dispatched easily, InteractsWithSockets provides socket interaction capabilities, and SerializesModels handles model serialization when broadcasting data.

The broadcastOn method specifies the channels on which the event should be broadcast. In this case, we are broadcasting on the chatroom channel. The broadcastAs method defines the name of the event listeners on the front end will use to catch this event.

With the MessageSent event created, your Laravel application can now broadcast messages in real time, enabling your chat application to provide a dynamic and interactive user experience.

Defining Private Channels in channels.php

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.

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. The : symbol denotes passing data to the component.

We pass two variables:

  1. :friend containing the chat partner's information retrieved from $friend.

  2. :current-user containing the currently authenticated user's information obtained using auth()->user().

Chat Component:

This Vue.js component, ChatComponent.vue, manages the chat interface functionality within the chat.blade.php view.

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.

<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>

Firing Up Your Laravel Chat Application with Vite

With the chat application's backend and front end now set up, it's time to fire up the application and see the real-time messaging in action. This step involves launching Laravel's real-time event broadcasting server with debugging enabled and utilizing Laravel Vite for a smooth development experience.

Starting Laravel's Real-Time Event Broadcasting Server

To kickstart real-time event broadcasting, you need to start Laravel's event broadcasting server, Reverb. This server will listen for events—like sending a message—and broadcast them to all connected clients in real-time. Open your terminal or command line interface and execute the following command:

php artisan reverb:start --debug

The --debug flag is crucial as it provides immediate feedback and error logging directly in your console, helping you understand the flow of events and troubleshooting any issues that arise during development.

Compiling Assets with Laravel Vite

Laravel Vite is a modern, fast front-end build tool that enhances your development workflow. It's specially designed to work seamlessly with Laravel, providing hot module replacement (HMR) out of the box. This means you can make changes to your CSS or JavaScript, and see those changes reflected in the browser instantly without a full page reload.

To start Vite and compile your assets, run the following command:

npm run dev

Because we are using the Laravel Themer package, we need to run the following command:

npm run dev:themename

This command boots up Vite, compiles your assets, and watches for any changes you make to your files, reloading parts of the page as needed.

Testing the Chat Application

With the event broadcasting server running and your assets compiled, navigate to your Laravel application's local development URL. Typically, this is, usually, http://localhost:8000, unless you've configured a different port or are using a tool like Laravel Valet that might assign a different domain. Once there, you should see your chat application's interface. Try sending a message using the provided form. If everything is set up correctly, you should witness the message appearing in real-time—both for you and any other connected clients—without necessitating a page refresh. This remarkable demonstration showcases the prowess of Laravel's event broadcasting in conjunction with Vite's rapid development cycle.

Congratulations! You've now successfully constructed a fully operational, real-time chat application running locally. This meticulously crafted setup facilitates immediate feedback and iteration, an invaluable asset during the development phase.

Building Real-Time Chat Applications with Laravel Reverb and Vue 3

Conclusion: Embracing the Power of Real-Time Communication

In today's fast-paced digital landscape, real-time communication has become a fundamental aspect of modern web applications. By leveraging the power of Laravel Reverb and Vue.js, developers can create feature-rich, real-time chat applications that foster seamless communication and enhance user engagement.

Throughout this comprehensive guide, we've explored the intricacies of building a real-time chat application, from setting up the development environment and configuring the necessary components to implementing the core functionality and exploring advanced features.

As you embark on your journey to create real-time applications, remember to stay curious, embrace emerging technologies, and prioritize accessibility and inclusive design. The future of web development is ever-evolving, and by staying informed and adapting to new trends, you can ensure that your applications remain relevant, performant, and engaging for years to come.

Embrace the power of real-time communication, and let your applications transcend the boundaries of traditional web development, fostering connections and facilitating seamless interactions in the digital realm.

0

Please login or create new account to add your comment.

0 comments
You may also like:

15 Must-Know TypeScript Features to Level Up Your Development Skills

TypeScript has become the go-to tool for developers building scalable, maintainable JavaScript applications. Its advanced features go far beyond basic typing, giving developers (...)
Harish Kumar

JavaScript Best Practices: Tips for Writing Clean and Maintainable Code

JavaScript is one of the most versatile and widely used programming languages today, powering everything from simple scripts to complex web applications. As the language continues (...)
Harish Kumar

Ditch jQuery: Vanilla JS Alternatives You Need to Know

jQuery revolutionized web development by simplifying DOM manipulation, event handling, and animations. However, modern JavaScript (ES6 and beyond) now provides many built-in methods (...)
Harish Kumar

Shallow Copy vs Deep Copy in JavaScript: Key Differences Explained

When working with objects and arrays in JavaScript, it's crucial to understand the difference between shallow copy and deep copy. These concepts dictate how data is duplicated (...)
Harish Kumar

A Beginner’s Guide to Efficient Memory Use in JavaScript

Managing memory efficiently in JavaScript applications is essential for smooth performance, especially for large-scale or complex applications. Poor memory handling can lead to (...)
Harish Kumar

Exploring the New Features of JavaScript ES2024: A Look into the Future of Web Development

Discovering new functionality in programming languages is a bit like a holiday — it’s filled with anticipation and the excitement of exploring something new. With the proposed (...)
Harish Kumar