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
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');
$table->text('message_text');
$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'];
}
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([
'user' => request('user'),
'message_text' => request('message_text')
]);
event((new MessageCreated($chat))->dontBroadcastToCurrentUser());
});
What this does is saving ChatMessage
inside table and send it to pusher. Let’s connect to UI and check.
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. Yu can access full example in this git repo..
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
});