Chat app with laravel reverb

Building a Real-Time Chat App with Laravel Reverb: Unveiling Laravel Reverb

,

New Laravel 11 comes with a new Laravel reverb and we are creating a Chat App with Laravel Reverb Project for you to understand the concept and use case of laravel reverb to build a real-time chat application. In today’s digital world, real-time communication has become an essential feature for many web applications. From instant messaging to collaborative tools, users expect a seamless and responsive experience.

In this article, we’ll walk through the process of building a real-time chat application using Laravel 11 and Reverb. We’ll cover the installation, configuration, and implementation of the necessary components to create a fully functional chat interface.

Prerequisites for Create Chat App with Laravel Reverb

Before we dive into the tutorial, ensure that you have the following prerequisites installed on your development machine:

  • PHP (version 8.2 or higher)
  • Composer (for managing PHP dependencies)
  • Node.js (version 20 or Higher)
  • A database management system (e.g., MySQL, PostgreSQL)

Setting Up the Laravel Project

Let’s start by creating a new Laravel project for Chat App with Laravel Reverb and setting up the necessary dependencies.

click here to install Laravel via Xampp

Open your terminal and run the following command to create a new Laravel project:

composer create-project laravel/laravel:^11.0 laravel-chat-app

Navigate to the project directory:

cd laravel-chat-app

Configure your environment variables by editing the .env file. Set up your database credentials and other necessary configurations.

Set up env:

DB_CONNECTION=sqlite
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel-chat-app
DB_USERNAME=root
DB_PASSWORD=

Create Migration With Model

You can generate a model and a migration for the messages by using this single command:

php artisan make:model -m Message

set up the migration for the messages database table with this code:

//database/migrations/2024_03_25_000831_create_messages_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('messages', function (Blueprint $table) {
            $table->id();
            $table->foreignId('user_id')->constrained();
            $table->text('text')->nullable();
            $table->timestamps();
        });
    }

    public function down(): void {
        Schema::dropIfExists('messages');
    }
};

This PHP script is a Laravel migration file responsible for creating a database table named “messages”. It utilizes Laravel’s migration functionality to version-control the database schema. In the up() method, it creates the “messages” table with an auto-incrementing primary key column, a foreign key column referencing another table’s ID, a text column for message content, and timestamp columns for creation and updates. 

Then you’ll need to set up the Message’s model with the following code:

//app/Models/Message.php

<?php

namespace App\Models;

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

class Message extends Model
{
    use HasFactory;

    public $table = 'messages';
    protected $fillable = ['id', 'user_id', 'text'];

    public function user(): BelongsTo {
        return $this->belongsTo(User::class, 'user_id');
    }

    public function getTimeAttribute(): string {
        return date(
            "d M Y, H:i:s",
            strtotime($this->attributes['created_at'])
        );
    }
}

This PHP script defines a Laravel Eloquent model named “Message” within the “App\Models” namespace. It specifies the table name as “messages”. Additionally, it establishes a BelongsTo relationship with the User model, indicating that each message belongs to a user.

The getTimeAttribute method serves as a custom accessor, formatting the ‘created_at’ timestamp attribute into a human-readable date format. This script encapsulates the logic for interacting with message data in the application, including relationships and attribute accessors, enhancing readability and maintainability.

Run the migration command to migrate the message migration into the Database.

php artisan migrate

Adding Laravel UI and Authentication

To streamline the authentication process and provide a user interface, we’ll leverage the Laravel UI package.

Install the Laravel UI package:

composer require laravel/ui

Install and build the frontend assets:

php artisan ui react --auth

It may ask to overwrite the app/Http/Controllers/Controller.php, and you can go ahead and allow it:

The [Controller.php] file already exists. Do you want to replace it? (yes/no) [no]

This will create all the necessary routes, controllers, views, and methods.

npm install 
npm run build

This command will generate the necessary files for React-based authentication and create a basic user interface. If you run the build command you do not have to run the npm run dev command separately.

Setting Up Laravel Routes and Controllers

To handle the chat functionality on the server-side, we’ll create the necessary routes and controllers in Laravel.

Open the routes/web.php file and add the following routes:

<?php

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

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

Auth::routes();

Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])->name('home');

Auth::routes();

Route::get('/home', [App\Http\Controllers\HomeController::class, 'index'])->name('home');


//message routes

Route::get('/messages', [HomeController::class, 'messages'])->name('messages');
Route::post('/message', [HomeController::class, 'message'])->name('message');

These routes will handle fetching messages and sending new messages, respectively.

Open the app/Http/Controllers/HomeController.php file and add the following methods:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Jobs\SendMessage;
use App\Models\Message;
use App\Models\User;
use Illuminate\Http\JsonResponse;

class HomeController extends Controller
{
    public function __construct() {
        $this->middleware('auth');
    }

    public function index() {
        $user = User::where('id', auth()->id())->select([
            'id', 'name', 'email',
        ])->first();

        return view('home', [
            'user' => $user,
        ]);
    }

    public function messages(): JsonResponse {
        $messages = Message::with('user')->get()->append('time');

        return response()->json($messages);
    }

    public function message(Request $request): JsonResponse {
        $message = Message::create([
            'user_id' => auth()->id(),
            'text' => $request->get('text'),
        ]);
        SendMessage::dispatch($message);

        return response()->json([
            'success' => true,
            'message' => "Message created and job dispatched.",
        ]);
    }
}

These methods handle fetching messages from the database and creating new messages, respectively. Additionally, we’ll dispatch a job to broadcast the new message to all connected clients.

Setting Up Laravel Events and Jobs

To handle real-time messaging, we’ll create a new Laravel event and job.

Generate a new event by running the following command:

php artisan make:event RecieveMessage

Open the app/Events/RecieveMessage.php file and update the broadcastOn method to return the appropriate WebSocket channel:

//app/Events/RecieveMessage.php

<?php

namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;

class RecieveMessage implements ShouldBroadcast
{
    use Dispatchable, InteractsWithSockets, SerializesModels;

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

    /**
     * Get the channels the event should broadcast on.
     *
     * @return array<int, \Illuminate\Broadcasting\Channel>
     */
    public function broadcastOn(): array
    {
        return [
            new PrivateChannel('channel_for_everyone'),
        ];
    }
}

Generate a new job by running the following command:

php artisan make:job SendMessage

Open the app/Jobs/SendMessage.php file and update the handle method to dispatch the RecieveMessage event:

//app/Jobs/SendMessage.php

<?php

namespace App\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use App\Events\RecieveMessage;
use App\Models\Message;

class SendMessage implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

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

    /**
     * Execute the job.
     */
    public function handle(): void
    {
        RecieveMessage::dispatch([
            'id' => $this->message->id,
            'user_id' => $this->message->user_id,
            'text' => $this->message->text,
            'time' => $this->message->time,
        ]);
    }
}

This job will be responsible for broadcasting the new message to all connected clients.

Setup WebSocket Channels

After installing Reverb, a channel needs to be added to the channels.php file. This channel can be defined by adding the following code snippet:

//routes/channels.php

use Illuminate\Support\Facades\Broadcast;

Broadcast::channel('channel_for_everyone', function ($user) {
    return true;
});

This script creates a channel named ‘channel_for_everyone’. Inside the closure, it currently returns true for any user, allowing unrestricted access to the channel. However, you can later modify this logic to impose restrictions on channel subscriptions as needed. Additionally, you have the flexibility to customize the channel name and its behavior according to your application’s requirements.

Installing Reverb

Reverb is a powerful WebSocket server that seamlessly integrates with Laravel, enabling real-time communication features. Let’s install and configure Reverb in our project.

Install Reverb using the following Composer command:

composer require beyondco/laravel-reverb

After the installation is complete, you’ll be prompted to configure Reverb. Follow the on-screen instructions to set up the required environment variables.

Once the configuration is complete, run the following command to generate the necessary files and configurations for Reverb:

php artisan install:broadcasting

This command will set up the required WebSocket channels and configurations for your application.

After execution of this command check that this configuration is automatically added in your env or not.

REVERB_APP_ID=510765
REVERB_APP_KEY=mj8gj3msdwiqh4tl05zr
REVERB_APP_SECRET=zdfm6vvotnkx5zzysije
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}"

Creating the Chat Components

Now that we have the necessary dependencies and configurations in place, let’s create the React components for our Chat App with Laravel Reverb. You can modify this as you want.

To customize Laravel views, follow these steps. Once your backend is set up, shift your focus to the front end. Before diving into React development, ensure you configure Laravel’s *.blade.php views properly. In your home blade view, ensure you have the root div with an ID of “main” to render all React components there. Here’s an example:

//resources/views/home.blade.php

@extends('layouts.app')

@section('content')
    <div class="container">
        <div id="main" data-user="{{ json_encode($user) }}"></div>
    </div>
@endsection

Create a new folder named components inside the resources/js directory.

Inside the components folder, create the following files:

  • Main.jsx
  • ChatBox.jsx
  • Message.jsx
  • MessageInput.jsx

These files will contain the React components responsible for rendering the chat interface, individual messages, and the message input field, respectively.

Here’s the code for the Main.jsx component (file path: resources/js/components/Main.jsx):

import React from 'react';
import ReactDOM from 'react-dom/client';
import '../../css/app.css';
import ChatBox from "./ChatBox.jsx";

if (document.getElementById('main')) {
    const rootUrl = "http://127.0.0.1:8000";
    
    ReactDOM.createRoot(document.getElementById('main')).render(
        <React.StrictMode>
            <ChatBox rootUrl={rootUrl} />
        </React.StrictMode>
    );
}

The Message.jsx component (file path: resources/js/components/message.jsx):

import React from "react";

const Message = ({ userId, message }) => {
    return (
        <div className={`row ${
        userId === message.user_id ? "justify-content-end" : ""
        }`}>
            <div className="col-md-6">
		<small className="text-muted">
                    <strong>{message.user.name} | </strong>
                </small>
                <small className="text-muted float-right">
                    {message.time}
                </small>
                <div className={`alert alert-${
                userId === message.user_id ? "primary" : "secondary"
                }`} role="alert">
                    {message.text}
                </div>
            </div>
        </div>
    );
};

export default Message;

The MessageInput.jsx component (file path: resources/js/components/MessageInput.jsx):

import React, { useState } from "react";

const MessageInput = ({ rootUrl }) => {
    const [message, setMessage] = useState("");

    const messageRequest = async (text) => {
        try {
            await axios.post(`${rootUrl}/message`, {
                text,
            });
        } catch (err) {
            console.log(err.message);
        }
    };

    const sendMessage = (e) => {
        e.preventDefault();
        if (message.trim() === "") {
            alert("Please enter a message!");
            return;
        }

        messageRequest(message);
        setMessage("");
    };

    return (
        <div className="input-group">
            <input onChange={(e) => setMessage(e.target.value)}
                   autoComplete="off"
                   type="text"
                   className="form-control"
                   placeholder="Message..."
                   value={message}
            />
            <div className="input-group-append">
                <button onClick={(e) => sendMessage(e)}
                        className="btn btn-primary"
                        type="button">Send</button>
            </div>
        </div>
    );
};

export default MessageInput;

Here’s the code for the ChatBox.jsx component (file path: resources/js/components/ChatBox.jsx):

import React, { useEffect, useRef, useState } from "react";
import Message from "./message.jsx";
import MessageInput from "./MessageInput.jsx";

const ChatBox = ({ rootUrl }) => {
    const userData = document.getElementById('main')
        .getAttribute('data-user');

    const user = JSON.parse(userData);
    // `App.Models.User.${user.id}`;
    const webSocketChannel = `channel_for_everyone`;

    const [messages, setMessages] = useState([]);
    const scroll = useRef();

    const scrollToBottom = () => {
        scroll.current.scrollIntoView({ behavior: "smooth" });
    };

    const connectWebSocket = () => {
        window.Echo.private(webSocketChannel)
            .listen('RecieveMessage', async (e) => {
                // e.message
                await getMessages();
            });
    }

    const getMessages = async () => {
        try {
            const m = await axios.get(`${rootUrl}/messages`);
            setMessages(m.data);
            setTimeout(scrollToBottom, 0);
        } catch (err) {
            console.log(err.message);
        }
    };

    useEffect(() => {
        getMessages();
        connectWebSocket();

        return () => {
            window.Echo.leave(webSocketChannel);
        }
    }, []);

    return (
        <div className="row justify-content-center">
            <div className="col-md-8">
                <div className="card">
                    {/* <div className="card-header bg-primary bg-gradient bg-opacity-75 fw-bold">Chat Book</div> */}
                    <div className="card-body"
                         style={{height: "500px", overflowY: "auto"}}>
                        {
                            messages?.map((message) => (
                                <Message key={message.id}
                                         userId={user.id}
                                         message={message}
                                />
                            ))
                        }
                        <span ref={scroll}></span>
                    </div>
                    <div className="card-footer">
                        <MessageInput rootUrl={rootUrl} />
                    </div>
                </div>
            </div>
        </div>
    );
};

export default ChatBox;

You can refer to the provided code snippets for the functionality of these components. However, feel free to customize and enhance the components based on your application’s requirements.

After completing the necessary configurations and implementations, it’s time to run the chat application.

Run the Application

Build the frontend assets by running the following command:

npm run build

Start the Laravel queue worker to process broadcasting jobs:

php artisan queue:listen

Start the Reverb WebSocket server:

php artisan reverb:start

Finally, start the Laravel development server:

php artisan serve

Now, you can access the chat application by visiting http://127.0.0.1:8000 in your web browser.

Conclusion

🌟In this tutorial, we’ve covered the steps to build a Chat App with Laravel ReverbπŸš€. We’ve explored the installation and configuration of Reverb, created React components for the chat interface, set up Laravel routes and controllers, and implemented Laravel events and jobs for real-time messaging. You can access this GithubπŸ’» profile for full code

Building real-time applications can be a challenging task, but with the help of Laravel and Reverb, the process becomes more streamlined and efficient. This tutorial provides a solid foundation for building more advanced real-time features in your web applications.πŸ‘

Remember, this is just the beginning. You can further enhance the chat application by adding features like private messaging, file sharing, and more.😍 Additionally, you can explore other real-time use cases, such as collaborative document editing, online gaming, and real-time data visualization.

Happy coding!πŸ˜„