Building a real-time chat room with Larasocket, Tailwind, Alpine, Livewire, and Laravel
https://ift.tt/30OkWAT
Laravel has a new Broadcaster: Larasocket. Today, we are going to see how we can use Larasocket with Tailwind, Alpine, and Livewire to build a beautiful, and lightweight real-time chat room. You can view the complete tutorial here.
What is the TALL stack?
Tailwind: Made by Adam Wathan. A powerful, lightweight, CSS library made up of utility classes. Utility classes offer all the power of normal CSS in a class-based interface for developers. Need to add margin-right
? Just add the class mr-{amount}
Alpine: Made by Caleb Porzio. A lightweight javascript library for simple client-side logic without needing the bulky Vue or React frameworks.
Livewire: Made by Caleb Porzio. A powerful library to help the abstract client to server communication. Need to make an AJAX request to submit a form? Not anymore. Livewire offers an easy way to call PHP code, right from the clients, as well as re-render to UI based on backend changes.
Laravel: From laravel.com: “Laravel is a web application framework with expressive, elegant syntax. We’ve already laid the foundation — freeing you to create without sweating the small things.”
Getting Started
We can quickly get started using a few helpful commands on the CLI:
laravel new larasocket-tall-demo
cd larasocket-tall-demo
composer require livewire/livewire laravel-frontend-presets/tall
php artisan ui tall --auth
For any Laravel application that uses Broadcasting, we need to uncomment this line in config/app.php
:
// App\Providers\BroadcastServiceProvider::class,
Broadcasting with Larasocket
Now that our Laravel application is off the ground, let’s bring in Larasocket to use as our broadcaster. It’s free to get started 💪
composer require larasocket/larasocket-driver
npm i laravel-echo larasocket-js
Over in config/broadcasting.php
we need to add the Larasocket driver as an option:
'larasocket' => [
'driver' => 'larasocket',
'token' => env('LARASOCKET_TOKEN'),
],
And let’s update bootstrap.js
to use Larasocket instead of the default Pusher:
import Echo from 'laravel-echo';
import Larasocket from 'larasocket-js';
window.Echo = new Echo({
broadcaster: Larasocket,
token: process.env.MIX_LARASOCKET_TOKEN,
});
The last thing we need to do for Larasocket is to update our .env
file:
BROADCAST_DRIVER=larasocket
LARASOCKET_TOKEN=<token>
MIX_LARASOCKET_TOKEN="${LARASOCKET_TOKEN}"
Just remember to replace <token>
with your token from Larasocket. They are free!
Chat
Time for the fun part, building our chat room.
php artisan make:model Message -m
php artisan make:event MessageSentEvent
php artisan make:livewire ChatRoom
We can now update the newly createdcreate_messages_table
migration with:
Schema::create('messages', function (Blueprint $table) {
$table->id();
$table->text('body');
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
$table->timestamps();
});
We are all done making database changes so let’s persist these:
php artisan migrate
Let’s add our column and relationship to Message.php
:
class Message extends Model
{
protected $fillable = [
'body'
];
public function user()
{
return $this->belongsTo(User::class);
}
}
Over in User.php
we can add the messages
relationship:
public function messages()
{
return $this->hasMany(Message::class);
}
Laravel Events
provide a great interface to send data in the socket messages. Let’s update our newly created MessageSentEvent
to pass along the needed data:
class MessageSentEvent implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels;
/**
* @var Message
*/
private $message;
/**
* Create a new event instance.
*
* @return void
*/
public function __construct(Message $message)
{
$this->message = $message;
}
/**
* Get the channels the event should broadcast on.
*
* @return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PresenceChannel('demo');
}
/**
* JSON data to broadcast with this message
*/
public function broadcastWith()
{
return $this->message->toArray();
}
}
Later on, we will dispatch this event when the user submits their message.
Important: don’t forget the implements ShouldBroadcast
! This interface tells Laravel to use the Broadcaster to send out socket messages using whichever driver you have configured.
There is just one last task before our backend is ready. We need to secure the demo
channel using routes/channels.php
:
Broadcast::channel('demo', function ($user) {
return [
'id' => $user->id,
'name' => $user->name,
];
});
Front-End
Remember that ChatRoom
Livewire component we made earlier? It is now time to put it to work. The first thing we need to do is create a new route in our routes/web.php
file:
Route::livewire('chat', 'chat-room')->middleware('auth')->layout('layouts.auth');
When you have a simple route and don’t need a method in a controller to return your view, you can use Route:view('path', 'myview')
as a route.
Laravel Livewire comes with a nifty helper that is very similar: Route::livewire('path','mylivewireviewcomponent')
. This is what we are using above.
Next, let’s build out the functionality:
class ChatRoom extends Component
{
public $messages = [];
public $here = [];
protected $listeners = [
'echo-presence:demo,here' => 'here',
'echo-presence:demo,joining' => 'joining',
'echo-presence:demo,leaving' => 'leaving',
];
public function render()
{
return view('livewire.chat-room');
}
public function mount()
{
$this->messages = Message::
with('user')
->latest()
->limit(30)
->get()
->reverse()
->values()
->toArray();
}
public function sendMessage($body)
{
if (! $body) {
$this->addError('messageBody', 'Message body is required.');
return;
}
$message = Auth::user()->messages()->create([
'body' => $body,
]);
$message->load('user');
broadcast(new MessageSentEvent($message))->toOthers();
array_push($this->messages, $message);
}
/**
* @param $message
*/
public function incomingMessage($message)
{
// get the hydrated model from incoming json/array.
$message = Message::with('user')->find($message['id']);
array_push($this->messages, $message);
}
/**
* @param $data
*/
public function here($data)
{
$this->here = $data;
}
/**
* @param $data
*/
public function leaving($data)
{
$here = collect($this->here);
$firstIndex = $here->search(function ($authData) use ($data) {
return $authData['id'] == $data['id'];
});
$here->splice($firstIndex, 1);
$this->here = $here->toArray();
}
/**
* @param $data
*/
public function joining($data)
{
$this->here[] = $data;
}
}
Laravel Livewire is doing a lot behind the scenes for us.
First, note the protected $listeners
array. Livewire uses this array to automatically subscribe to channels (uses Echo, in Javascript) for us. You can read more about that here. We will use the standard here
, join
, leaving
presence channel events to keep track of who is in the chat room.
Currently, there is a Livewire bug that limits the presence channels we can subscribe to. It only allows those 3. Not a problem, we will leverage Alpine to easily subscribe to more channels in the Javascript. Let’s create the UI to go with our ChatRoom
. In chat-room.blade.php
let’s update it with:
@section('title', 'Larasocket Demo')
<div
class="mt-4 bg-white rounded-lg shadow-md p-6"
x-data=""
x-init="
Echo.join('demo')
.listen('MessageSentEvent', (e) => {
@this.call('incomingMessage', e)
})
">
<div class="flex flex-row flex-wrap border-b">
<div class="text-gray-600 w-full mb-4">Members:</div>
@forelse($here as $authData)
<div class="px-2 py-1 text-white bg-blue-700 rounded mr-4 mb-4">
</div>
@empty
<div class="py-4 text-gray-600">
It's quiet in here...
</div>
@endforelse
</div>
<template x-if="messages.length > 0">
<template
x-for="message in messages"
:key="message.id"
>
<div class="my-8">
<div class="flex flex-row justify-between border-b border-gray-200">
<span class="text-gray-600" x-text="message.user.name"></span>
<span class="text-gray-500 text-xs" x-text="message.created_at"></span>
</div>
<div class="my-4 text-gray-800" x-text="message.body"></div>
</div>
</template>
</template>
<template x-if="messages.length == 0">
<div class="py-4 text-gray-600">
It's quiet in here...
</div>
</template>
<div
class="flex flex-row justify-between"
>
<input
@keydown.enter="
@this.call('sendMessage', messageBody)
messageBody = ''
"
x-model="messageBody"
class="mr-4 shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
type="text"
placeholder="Hello World!">
<button
@click="
@this.call('sendMessage', messageBody)
messageBody = ''
"
class="btn btn-primary self-stretch"
>
Send
</button>
</div>
@error('messageBody') <div class="error mt-2"></div> @enderror
</div>
Note that we are using Tailwind to help construct a pleasant UI, easily. Tailwind provides a nice library of CSS classes to easily update margin, padding, borders, display, colors, everything, and anything. Styling our elements in using Tailwind has a few key advantages:
- Lightweight
- Readable class names
- Standardizes styling across organizations
- Customizable
- More
We are now ready to give our application using the Tailwind, AlpineJS, Livewire, and Laravel stack a go.
Let’s compile our front-end assets:
npm install
npm run dev
… and run our server:
php artisan serve
You can navigate to http://127.0.0.1:8000/chat and start playing with Larasocket and your chat room.
Related:
programming
via Laravel News Links https://ift.tt/2dvygAJ
August 11, 2020 at 09:09PM