Chat online con Laravel 5.5 + Pusher + Vue

Claudio Vallejo
9 min readDec 2, 2017

--

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

abrir tu editor o IDE preferido y editar los datos correspondientes a tu BD (estos acá son solo una muestra)

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.

acceder a tu base de datos local con tus credenciales
una vez dentro, selecciona crear una base de datos y luego escribe el nombre de tu nueva base de datos y la codificación que deseas utilizar.

Una vez configurado los accesos correctamente, es momento de correr la migración :

php artisan migrate
ejecutado el comando, debieras ver una mensaje similar a este

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
mensaje de salida al crear el modelo del ‘Message’, su controlador y migración

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’];
el modelo ‘message’ debiera quedar así

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);
}

}
modelo ‘Message’

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
comando para crear un evento

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í:

configuración de pusher en nuestro archivo .env

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:

escribe tus propias credenciales

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.

configurar opciones adicionales

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í:

recuerda incorporar tu KEY de Pusher

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
screen de ambas ventanas en navegadores y sesiones distintas funcionando 100%

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');
archivo app.js con la adición

Ahora corre el siguiente comando para que puedas ver los cambios:

npm run dev

voilá! todo funcionando y con un estilo más adecuado ;)

--

--

Claudio Vallejo

Wine, programmer, diver & photographer lover. Agronomist Engineer. CEO/Founder of Intelvid.la — Cofounder Eziviu.com More: cvallejo.me