In the digital age, real-time web applications have transformed how we interact with technology and each other. From live chat systems to instant content updates, users now expect seamless, instantaneous communication at their fingertips. This tutorial will introduce you to the exciting world of real-time web applications using Laravel and Pusher, two powerful tools that make implementing real-time features a breeze.
Install and run migrations
First let us install the laravel.
composer create-project laravel/laravel .
For the ease of use let's use SQLite database. In .env change nysql to sqlite here. And remove DB_DATABASE=laravel
in .env
(And make sure to comment this - DB_DATABASE=laravel if you use sqlite)
DB_CONNECTION=sqlite
Now let's write migrations.
php artisan make:migration create_chat_messages_table --create=chat_messages
Now add this to migration table
public function up(): void
{
Schema::create('chat_messages', function (Blueprint $table) {
$table->id();
$table->string('user')->nullable();
$table->text('message_text')->nullable();
$table->timestamps();
});
}
now run
php artisan migrate
Pusher
First log into pusher and create an app.
https://dashboard.pusher.com/apps
After creating the app get the these fields and save in side .env
BROADCAST_DRIVER=pusher
...
PUSHER_APP_ID=""
PUSHER_APP_KEY=""
PUSHER_APP_SECRET=""
PUSHER_APP_CLUSTER=
install pusher add this inside command line
composer require pusher/pusher-php-server
Vue JS
Now let's build the UI with VusJS.
npm install
npm install vue@latest vue-loader@latest
npm i @vitejs/plugin-vue
Edit File vite.config.js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [
vue(),
laravel({
input: ['resources/css/app.css', 'resources/js/app.js'],
refresh: true,
}),
],
});
Now got to resources\js\app.js
and add these lines.
import { createApp } from 'vue';
import Chat from './components/Chat.vue';
import App from './App.vue'
const app = createApp(App);
app.component('Chat', Chat);
app.mount("#app");
Let's start with UI
And create this file resources\js\components\Chat.vue
Add these lines
<template>
<div class="chat-box">
<div class="chat-box-header">Chat</div>
<div class="chat-box-messages" id="chat-messages">
<div class="message other-user">Hi, how are you?</div>
<div class="message current-user">I'm good, thanks! And you?</div>
</div>
<div class="chat-box-input">
<input type="text" class="input_border" placeholder="Type a message..." />
<button type="button">Send</button>
</div>
</div>
</template>
<style scoped>
.chat-box-input {
padding: 10px;
background-color: #fff;
border-top: 1px solid #eee;
display: flex; /* Aligns input and button side by side */
}
.chat-box-input input {
flex-grow: 1; /* Allows input to take up available space */
margin-right: 8px; /* Adds spacing between input and button */
padding: 8px 10px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
.chat-box-input button {
padding: 10px 15px;
background-color: #007bff;
color: #ffffff;
border: none;
border-radius: 4px;
cursor: pointer; /* Changes cursor to pointer on hover */
white-space: nowrap; /* Prevents wrapping of text in the button */
}
.chat-box-input button:hover {
background-color: #0056b3; /* Darker shade on hover for visual feedback */
}
.chat-box {
display: flex;
flex-direction: column;
max-width: 320px;
min-width: 300px;
height: 500px;
border: 1px solid #ccc;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
font-family: Arial, sans-serif;
background-color: #fff;
}
.chat-box-header {
background-color: #007bff;
color: #ffffff;
padding: 10px;
text-align: center;
font-size: 16px;
}
.chat-box-messages {
flex: 1;
padding: 10px;
overflow-y: auto;
background-color: #f9f9f9;
display: flex;
flex-direction: column;
}
.message {
margin-bottom: 12px;
padding: 8px 10px;
border-radius: 20px;
display: inline-block;
max-width: 70%;
}
.current-user {
background-color: #007bff;
color: #ffffff;
margin-left: auto;
text-align: right;
align-self: flex-end;
}
.other-user {
background-color: #e9ecef;
color: #333;
}
.chat-box-input {
padding: 10px;
background-color: #fff;
border-top: 1px solid #eee;
}
.chat-box-input input {
width: 100%;
padding: 8px 10px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
.input_border{
border: 1px solid #ccc;
}
</style>
Now go to this file resources\views\welcome.blade.php
delete everything and add these,
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>Laravel</title>
@vite('resources/js/app.js')
</head>
<body class="antialiased">
<div id="app"></div>
</body>
</html>
Add this file resources\js\App.vue
Fill with these content,
<template>
<chat></chat>
</template>
Run this command in one console (in your project directory)
php artisan serve
Run this command in another console (in your project directory)
npm run dev
Now you will see this fine UI in localhost:8000
Now let's create a model and event.
php artisan make:model ChatMessage
php artisan make:event MessageCreated
This is ChatMessage Model
class ChatMessage extends Model
{
use HasFactory;
protected $fillable = ['user', 'message_text'];
}
Then 'MessageCreated' event.
<?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 MessageCreated implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $chat;
/**
* Create a new event instance.
*/
public function __construct($chat)
{
$this->chat = $chat;
}
/**
* Get the channels the event should broadcast on.
*
* @return array<int, \Illuminate\Broadcasting\Channel>
*/
public function broadcastOn(): array
{
return [
new Channel('chats'),
];
}
}
Check here we have added ShouldBroadcast
broadcasting on a private channel. Broadcasting on public channel means when use echo anyone will see that.
And need to enable providers too
config\app.php
And now the routes.
Route::post('/chat', function () {
$chat = ChatMessage::create([
'message_text' => request('message_text')
]);
event((new MessageCreated($chat))->dontBroadcastToCurrentUser());
});
What this does is saving ChatMessage
inside table and send it to pusher.
And I changed the resources\js\components\Chat.vue
also. Keep the <style>
tag and replace all other code with this
<template>
<div class="chat-box">
<div class="chat-box-header">Chat</div>
<div class="chat-box-messages" id="chat-messages">
<div v-for="message in messages" :key="message.message_text"
:class="message.user == username ? 'message current-user' : 'message other-user'">
{{ message.message_text }}
</div>
</div>
<div class="chat-box-input">
<input type="text" v-model="newMessage" class="input_border" placeholder="Type a message..." />
<button type="button" @click="setMessage">Send</button>
</div>
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue';
const messages = ref([]);
const newMessage = ref('');
const setMessage = () => {
axios.post('/chat', {
message_text: newMessage.value
}).then(() => {
let message = {
message_text: newMessage.value
}
messages.value.push(message);
newMessage.value = "";
});
}
</script>
What this does is when you click Send button send call the Route::post('/chat',
route. And there inside the event call they send API call to the pusher.
Now if you check in ui by clicking the button
You will see this in pusher dashboard.
But you may notice that after pressing the button it takes long time to populate the message in screen. That happens it takes long time to send pusher by backend. We can make this call asynchronous.
This is how we does that,
Step 1: Configure the Queue Driver
First, update your .env file to use the database queue driver:
QUEUE_CONNECTION=database
This setting tells Laravel to use the database for queueing jobs.
Step 2: Create the Queue Table
Laravel needs a table in your database to store queued jobs. You can create this table by running the queue table migration that comes with Laravel. In your terminal, execute:
php artisan queue:table
Then, apply the migration to create the table:
php artisan migrate
Step 4: Ensure Event Listeners Implement ShouldQueue
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class MessageCreated implements ShouldBroadcast, ShouldQueue
{
Step 5: Running the Queue Worker
php artisan queue:work
This command starts a queue worker that listens for new jobs on the database queue and processes them. Keep this worker running to ensure your queued jobs are processed.
Now you see significant speed increase in API call
I have added few changes to chat interface. You can access full example in this git repo..
https://github.com/vimuths123/chatapp
Run this in cli
npm install --save laravel-echo pusher-js
And you need to add this to resources\js\bootstrap.js
import Echo from 'laravel-echo';
import Pusher from 'pusher-js';
window.Pusher = Pusher;
window.Echo = new Echo({
broadcaster: 'pusher',
key: 'pubkey',
cluster: 'mt1',
forceTLS: true
});
And don't forget to replace *pubkey * with your own pusher key
Change the routes file (routes\web.php) to this.
Route::post('/chat', function () {
$chat = ChatMessage::create([
'user' => request('user'),
'message_text' => request('message_text')
]);
event((new MessageCreated($chat))->dontBroadcastToCurrentUser());
});
And replace and sections of resources\js\components\Chat.vue
file to this
<template>
<div v-if="!username">
<input type="text" v-model="tempUsername" placeholder="Enter your username">
<button @click="setUsername">Join Chat</button>
</div>
<div class="chat-box" v-else>
<div class="chat-box-header">Chat</div>
<div class="chat-box-messages" id="chat-messages">
<div v-for="message in messages" :key="message.message_text"
:class="message.user == username ? 'message current-user' : 'message other-user'">
{{ message.message_text }}
</div>
</div>
<div class="chat-box-input">
<input type="text" v-model="newMessage" class="input_border" placeholder="Type a message..." />
<button type="button" @click="setMessage">Send</button>
</div>
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue';
const username = ref('');
const tempUsername = ref('');
const messages = ref([]);
const newMessage = ref('');
const setUsername = () => {
username.value = tempUsername.value.trim();
tempUsername.value = '';
};
const setMessage = () => {
axios.post('/chat', {
user: username.value,
message_text: newMessage.value
}).then(() => {
let message = {
user: username.value,
message_text: newMessage.value
}
messages.value.push(message);
newMessage.value = "";
});
}
onMounted(async () => {
window.Echo.channel('chats').listen('MessageCreated', (e) => {
let message = {
user: e.chat.user,
message_text: e.chat.message_text
}
messages.value.push(message);
});
});
</script>
Here we have added the two users and added window.Echo.channel('chats').listen('MessageCreated', (e) => {
So always code listens to chats channel.
And also there is a little performance optimization we can do for improve this. We could use redis to store these jobs instead of database.
Redis operates in memory, offering much faster read and write operations compared to disk-based systems. This is crucial for chat applications where timely processing of messages and notifications is critical for user experience.
By using Redis for queue management and temporary data storage (e.g., unread messages or active users), you can significantly reduce the load on your main database, reserving it for more critical tasks like persisting chat logs or user information.
Let's check how to use redis for job queues
First install predis
composer require predis/predis
Then let's tell laravel to use redis for queue. Add this in .env
REDIS_CLIENT=predis
QUEUE_CONNECTION=redis
And don't forget to install redis on your system before
Top comments (1)
is this user to another user chat system?
your code not working when user to user