Chat online con Laravel 5.5 + Pusher + Vue
He encontrado un excelente post realizado por Jaouad Ballat que estimo de alta relevancia exponerlo traducido al español para todos los programadores de Laravel hispanoparlantes.
Como siempre, lo primero es crear una nueva aplicación con Laravel. Para ello el comando utilizado es:
laravel new laravel-chat
Ingresa en tu nueva aplicación con el comando:
$ cd laravel-chat
Sistema de Autenticación
Ahora es momento de proceder con el sistema de autenticación básico de Laravel (si buscas un sistema de autenticación con roles, te recomiendo leer mi post anterior: Autenticación de Usuarios y Roles)
Para crear nuestro sistema básico debemos escribir lo siguiente en nuestra consola:
php artisan make:auth
Recuerda agregar los datos correspondientes para acceder a la base de datos en tu archivo .env
Siempre es recomendable asegurarse que haz creado correctamente tu nueva base de datos. En este caso llamada chat.
En mi caso la forma de hacerlo es entrando a mi editor de bd de datos predilecto (Sequel Pro). Gratuito y muy potente… altamente recomendable.
Una vez configurado los accesos correctamente, es momento de correr la migración :
php artisan migrate
Creación del Modelo ‘Message’, del Controlador y su respectiva Migración
Ahora lo que debes hacer es crear el modelo, la migración y el controlador del ‘message’ en una sola línea de código:
php artisan make:model Message -m -c
Abre la nueva migración creada y agrega los siguientes campos a la tabla ‘message’:
public function up()
{
Schema::create(‘messages’, function (Blueprint $table) {
$table->increments(‘id’); $table->text(‘message’);
$table->integer(‘user_id’)->unsigned();
$table->foreign(‘user_id’)->references(‘id’)->on(‘users’); $table->timestamps();
});
}
La primera linea agrega el campo del mensaje y la segunda y tercera referencia al id del usuario que envía el mensaje (‘message’) mediante una clave foránea y su relación de referencia a la tabla usuarios.
Agregar estos campos como fillable en el model ‘Message’:
protected $fillable = [‘message’, ‘user_id’];
Correr nuevamente el comando para crear la migración:
php artisan migrate
Establecer las relaciones:
Un usuario podrá mandar muchos mensajes, por ende hay que establecer una relación uno a muchos ‘one to many’.
Para ello adicionar en el modelo ‘Message’ la función pública de la relación con la clase User:
class Message extends Model
{
protected $fillable = ['message', 'user_id'];
public function user()
{
return $this->belongsTo(User::class);
}
}
Ahora hay que crear la relación inversa para el modelo ‘User’ dado que un usuario puede tener muchos mensajes. En este caso la relación a utilizar es hasMany. Quedaría el modelo User de la siguiente manera:
<?php
namespace App;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use Notifiable;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'name', 'email', 'password',
];
/**
* The attributes that should be hidden for arrays.
*
* @var array
*/
protected $hidden = [
'password', 'remember_token',
];
public function message()
{
return $this->hasMany(Message::class);
}
}
Adición de Rutas:
Al ingresar en la página de inicio se podrán mostrar todos los mensaje con un simple campo de texto que enviará los mensajes nuevos. Para ello el archivo de rutas deberá quedar así:
Auth::routes();
Route::get('/', 'MessageController@index');
Route::get('/messages', 'MessageController@fetch')->middleware('auth');
Route::post('/messages', 'MessageController@sentMessage')->middleware('auth');
*Considera que he asignado la vista ‘chat’ a la dirección /home.
La página de inicio mostrará los mensajes. A GET
messages
route will fetch all chat messages and a POST
messages
route will be used for sending new messages.
NOTE: Since we have removed the /home
route, you might want to update the redirectTo
property of both app/Http/Controllers/Auth/LoginController.php
and app/Http/Controllers/Auth/RegisterController.php
to:
protected $redirectTo = '/';
Hay que tener en cuenta que la función de fetch simplemente devolverá todos los mensajes con los usuarios almacenados en la base de datos, y la función sentMessage almacenará todos los mensajes.
Debemos agregar ahora ambas funciones al controlador MessageController:
<?php
namespace App\Http\Controllers;
use App\Events\MessageSentEvent;
use App\Message;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class MessageController extends Controller
{
public function fetch()
{
return Message::with('user')->get();
}
public function sentMessage(Request $request)
{
$user = Auth::user();
$message = Message::create([
'message' => $request->message,
'user_id' => Auth::user()->id,
]);
broadcast(new MessageSentEvent($user, $message))->toOthers();
}
}
Generar la Vista del Chat:
Crear un nuevo archivo llamado ‘chat.blade.php’ y escribir lo siguiente:
@extends('layouts.app')
@section('content')
<h1 class="text-center">Aplicación de Chat en Laravel</h1>
<message :messages="messages"></message>
<sent-message v-on:messagesent="addMessage" :user="{{ Auth::user() }}"></sent-message>
@endsection
Como podrás notar, hay algunas etiquetas personalizadas como: message
la que es un componente que mostrará nuestros mensajes. Además hay otra etiqueta llamada sent-message
dado que proveerá de un campo de texto y botón para poder enviar los mensajes.
Crear componente Message.vue:
Este componente en Vue, hay que crearlo en la carpeta resources/assets/js/components/ creamos el archivo Message.vue y agregamos lo siguiente:
<template>
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default" v-for="message in messages">
<div class="panel-heading"><strong>{{ message.user.name }}</strong></div>
<div class="panel-body">
<p>{{ message.message }}</p>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props: ['messages'],
mounted() {
console.log(this.messages)
}
}
</script>
<style scoped>
.panel{
margin-bottom: 0;
}
</style>
Crear componente Sent.vue:
Dentro de la misma carpeta (resources/assets/js/components/) crear el nuevo componente Sent.vue y agregar lo siguiente:
<template>
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-footer">
<form @submit.prevent.keyup="sent">
<div class="form-group">
<input type="text" class="form-control" v-model="message.message">
</div>
<div class="form-group">
<button type="submit" class=" btn btn-primary">Enviar mensaje</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props:['user'],
data() {
return {
message: {
message: '',
user: this.user
}
}
},
methods: {
sent () {
this.$emit('messagesent', this.message)
this.message = {}
}
}
}
</script>
Luego necesitamos registrar nuestros componentes en la instancia raíz de Vue. Abrir el archivo resources/assets/js/app.js
y actualizarlo con el siguiente código:
Vue.component('message', require('./components/Message.vue'));
Vue.component('sent-message', require('./components/Sent.vue'));
const app = new Vue({
el: '#app',
data: {
messages: []
},
mounted(){
this.fetchMessages();
Echo.private('chat')
.listen('MessageSentEvent', (e) => {
this.messages.push({
message: e.message.message,
user: e.user
})
})
},
methods: {
addMessage(message) {
this.messages.push(message)
axios.post('/messages', message).then(response => {
//console.log(response)
})
},
fetchMessages() {
axios.get('/messages').then(response => {
this.messages = response.data
})
}
}
});
Una vez que la instancia de Vue esté lista, usando Axios, podremos hacer una petición GET a la ruta ‘messages’ y buscar (fetch) todos los mensajes para pasarlos al array ‘messages’ que se visualizará en la vista del chat. El ‘addMessage’ recibe el mensaje emitido por el componente ‘Sent’ y lo envía (push) al arreglo (array) lo que genera la petición POST de la ruta ‘messages’ a los mensajes.
Crear el evento MessageSentEvent:
Para agregar la interacción en tiempo real (online) de nuestra aplicación de Chat, necesitamos algunos eventos de broadcast. En nuestro caso, utilizaremos el MessageSentEvent cuando un usuario envíe un mensaje. Primero, necesitamos crear un evento que llamaremos MessageSentEvent:
php artisan make:event MessageSentEvent
Abrir el archivo recién creado en la ruta app/Events/MessageSentEvent.php y agregar lo siguiente, asegurándose de implementar el ShouldBroadcast en la clase MessageSentEvent:
...
class MessageSentEvent implements ShouldBroadcast
{
use Dispatchable, InteractsWithSockets, SerializesModels; public $message;
public $user; /**
* Create a new event instance.
*
* @return void
*/
public function __construct($user, $message)
{
$this->user = $user;
$this->message = $message;
}
/**
* Get the channels the event should broadcast on.
*
* @return \Illuminate\Broadcasting\Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('chat');
}
}
Debido a que nuestra aplicación es un chat exclusivo para usuarios autenticados, hemos creado un canal privado llamado ‘chat’, el cual solo estará disponible para los usuarios que se hayan registrado en nuestra aplicación.
Para ello necesitamos autorizar a los usuarios registrados para que puedan ‘escuchar’ dicho canal. Esto se debe realizar en el siguiente archivo ‘routes/channels.php’ con lo siguiente:
Broadcast::channel('chat', function ($user) {
return Auth::user();
});
Configurando Pusher:
Necesitamos indicarle a nuestra aplicación en Laravel que utilizaremos el driver de Pusher. Para ello debemos especificarlo en nuestro archivo ’.env’ el que debe quedar así:
Abrir el archiv config/app.php'
y sacar los comentarios al proveedorApp\Providers\BroadcastServiceProvider::class,
que se encuentra en el arreglo ‘Application Service Providers…’.
Aún necesitamos instalar el SDK PHP de Pusher. Esto lo podemos realizar a través del siguiente comando de composer:
composer require pusher/pusher-php-server "~3.0"
Ahora es tiempo de crear nuestra (primera) cuenta gratuita en Pusher https://pusher.com luego ingresar al panel de control y crear una nueva app.
Si abres el archivo de configuración config/broadcasting.php
podrás darte cuenta que Laravel envía algunas credenciales de Pusher al archivo de configuración .env
Ahora debes actualizar el archivo .env
con las credenciales nuevas que te entrega Pusher al momento de crear tu nueva app:
Recuerda llenar la información del ‘cluster’ de tu aplicación nueva de Pusher y las opciones adicionales en el archivo de configuración additional config/broadcasting.php
.
El paso siguiente es instalar las dependencias:
npm install
Ahora lo que necesitamos es instalar también Laravel Echo, que es una librería en JavaScript que permite suscribirse y escuchar los eventos:
npm install --save laravel-echo pusher-js
Ahora necesitamos decirle a Laravel Echo que utilice Pusher. Al final del archivoresources/assets/js/bootstrap.js
, simplemente descomentar la sección de Laravel Echo y actualizarlo para que quede así:
Escuchando los Eventos:
Una vez que el evento MessageSentEvent se emite, necesitamos escucharlo para poder actualizar la ventana de chat con los nuevos mensajes enviados. Hacemos esto agregando el siguiente código en la funciónmounted()
del archivoresources/assets/js/app.js
:
Lo último que queda ahora es correr el siguiente comando:
npm run dev
Ahora nuestra aplicación de chat en laravel está lista y puedes comenzar a enviar y recibir mensajes en tiempo real ingresando a tu dirección local (si utilizas valet):
laravel-chat.dev
Bonus Track
Si deseas añadir en español la fecha de creación del mensaje utilizando Vue, debes seguir los siguientes pasos (muy simples).
Instala moment.js a través del siguiente comando:
npm install moment --save
Actualiza la vista del componente de Vue:
<template>
<div class="container">
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default" v-for="message in messages">
<div class="panel-heading"><strong>{{ message.user.name }}</strong></div>
<div class="panel-body">
<p>{{ message.message }}</p>
<span class="text-diff pull-right"><em>{{ message.created_at | timeago }}</em></span>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
moment.locale('es');
export default {
props: ['messages'],
mounted() {
console.log(this.messages)
},
filters: {
timeago(value) {
return moment.utc(value).local().fromNow()
}
}
}
</script>
<style scoped>
.panel{
margin-bottom: 0;
}
.text-diff {
font-size: 0.8em;
color: #cccccc;
}
</style>
Por último en el archivo resources/assets/js/app.js debes añadir la siguiente línea para adicionar moment a tu app.
window.moment = require('moment');
Ahora corre el siguiente comando para que puedas ver los cambios:
npm run dev
voilá! todo funcionando y con un estilo más adecuado ;)