Como integrar Laravel, NodeJS + Express + Socket.IO

Comunicação em tempo real

Sem o Redis.

Quis começar esse artigo dizendo exatamente isso: sem o Redis. Me deparei com uma situação em que precisava integrar o Laravel com o Socket.IO para realizar funcionalidades em tempo real.

Em todas as pesquisas que fazia encontrava artigos, até mesmo aqui no Medium, de como realizar o que eu queria, mas com Redis. Não é que eu não quisesse usar o Redis, mas não pra isso.

Eu já tinha em mente que teria que usar o NodeJS com o Express, porém, não queria me sentir obrigado — já se sentiu assim? — a usar outro recurso a mais, no caso, o Redis.

Laravel & NodeJS + Express + Socke.IO

Enough, show me the code!

Para que você possa entender o processo vou deixar abaixo um fluxograma com a regra do negócio.

Basicamente é isso. Por que usar eventos no Laravel? Por pelo menos dois motivos:

  • “Desafoga” o controller. Não precisamos fazer tudo na função no controller. Cria um evento e executa o que precisa nele.
  • O projeto fica mais organizado, limpo, legível e de fácil manutenção.

Vou partir da premissa que você já tem o Laravel instalado e o Node instalado e configurado no seu sistema operacional. A partir disso vamos instalar e configurar o servidor NodeJS com o Socket.IO.

No exemplo abaixo estarei utilizando a versão 5.6 do Laravel, mas se quiser usar outra não tem problema, desde que você saiba utilizar os recursos de cada versão.

Servidor NodeJS

  1. Crie uma pasta, pode ser irmã do seu projeto Laravel. Depois entre nela.
$ mkdir server-laravel-nodejs
$ cd server-laravel-nodejs

2. Instale as dependências.

$ npm install body-parser express socketio

3. Crie um arquivo chamado app.js e digite o seguinte código abaixo.

var express         = require("express");
var socketio = require("socket.io");
var bodyParser      = require('body-parser');
var app = express();
var http, server;
http    = require('http');
server = http.createServer(app);
var io              = socketio(server);
var clients = [];
/**
* Initialize Server
*/
app.use(bodyParser.urlencoded({
extended: true
}));
app.use(bodyParser.json());
server.listen(8888, function(){
console.log('Servidor Rodando na Porta 8888');
});
/**
* Página de Teste
*/
app.get('/', function(req, res){
res.send('Servidor Rodando...');
});
// Recebe requisição do Laravel
app.post('/like', function(req, res){
var params = req.body;
    clients.forEach(function(v, i){
var listen = v[0];
       // Dispara evento para os clientes     
listen.emit('like', params);
});
    res.send();
});
// Recebe conexão dos usuários no servidor
io.on('connection', function(client){
// Adicionado clientes
client.emit('welcome', null, function(data){
clients.push([this, data.id]);
});

console.log('Um cliente conectado');
});

4. Para testar o servidor NodeJS no terminal execute o comando abaixo e verifique a saída do comando.

$ node app.js
// Output: Servidor Rodando na Porta 8888

5. Para testar no navegador, acesse http://localhost:8888. Verifique a resposta no navegador.

Feito isso, seu servidor NodeJS está funcionando com sucesso. Mas ainda falta a página do cliente, do usuário que irá receber a resposta do servidor NodeJS.

Laravel — Página do Cliente

Vamos fazer algo idêntico a uma função Facebook. Vamos criar uma publicação fixa na página e dar um like. O cliente que estará usando outra instância do navegador receberá uma notificação em tempo real de que alguém curtiu sua publicação.

Muitos criariam um chat de exemplo, eu não.

Criei uma página simples com uma publicação e um botão CURTIR.

O diferencial é que dentro dessa página tem o código abaixo fazendo conexão com o servidor NodeJS na porta 8888.

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jquery-toast-plugin/1.3.2/jquery.toast.min.css">
<script src="https://code.jquery.com/jquery-1.11.1.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-toast-plugin/1.3.2/jquery.toast.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.1.1/socket.io.js"></script>
<script>
$(function () {
var socket = io('http://localhost:8888');
var urlBase = document.location.protocol + '//' + document.location.hostname + document.location.pathname;
      $('#like').click(function(event){
event.preventDefault();
            var self = $(this);
            // Envio um AJAX para o Laravel
$.ajax({
url: urlBase + 'like',
type: "POST",
data: {
name : self.data('name'),
},
success: function(result){
console.log('Sucesso!');
}
});
});
       // Registra usuário no Socket
socket.on('welcome', function(data, acknowledge){
acknowledge({
id: 1
});
});
       /**
* Recebe notificação de like e faz alert
*/
socket.on('like', function(response){
$.toast({
heading: 'Notificação de LIKE',
text: response.message,
loader: true,
hideAfter: 15000,
loaderBg: '#FF0000'
});
});
});
</script>

Estou usando jQuery para facilitar a programação e estou importando o jQuery e o Socket.IO para cliente como CDN. Mas você pode fazer como quiser, funcionará da mesma forma, desde que você se conecte no endereço correto do servidor.

Também estou colocando na página um CSS e um JS do Toast jQuery para alertas customizados.

Acesse o site na rota de sua preferência na qual criou essa view e veja no terminal que o NodeJS irá mostrar uma mensagem confirmando que tem alguém conectado. It’s is Socket.IO!

Terminal

Estou enviando uma requisição para o Laravel quando clico no botão CURTIR com um valor chamado name que estou pegando o valor do atributo data-name.

<a href="#" class="btn" id="like" data-name="Diego Souza">CURTIR</a>

Events

Para criar um evento execute o comando do artisan na raiz do projeto Laravel. Um arquivo será criado dentro da pasta app/Events/LikeEvent.php.

$ php artisan make:event LikeEvent

Criamos o evento, mas isso não basta. É preciso registrar no arquivo EventServiceProvider.php o evento e qual o arquivo que será executado quando o evento for escutado. Registre o evento no array $listen e no valor dele que é do tipo array já defina qual arquivo que será responsável por executar a função de quando esse evento for escutado. O arquivo será criado abaixo.

protected $listen = [
'App\Events\LikeEvent' => [
'App\Listeners\LikeListener', // Esse arquivo será criado abaixo
],
];

Depois disso crie o arquivo que será responsável por executar a função quando escutar o evento. Para isso execute o comando abaixo. O arquivo será criado em app/Listeners/LikeListener.php.

$ php artisan make:listener LikeListener

Dentro do arquivo tem o código abaixo. A função handler() já vai estar declarada dentro do arquivo, então é só completá-la. Esse parte do código que vai executar alguma coisa quando o evento for disparado e escutado. E é nesse código que faço conexão com o servidor NodeJS.

Perceba que tem a URL do servidor e é feita uma requisição via CURL no NodeJS. Concatenado a URL tem um endpoint que é a rota que será acessada no Express.

public function handle($event)
{
$urlSite = 'http://localhost:8888/';
$urlWebSocket = $urlSite.'/'.$event->endpoint;
    $dataParams     = http_build_query($event->params);
$ch = curl_init();
    try{
curl_setopt($ch, CURLOPT_URL, $urlWebSocket);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, TRUE);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_POSTFIELDS, $dataParams);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));
        $response   = curl_exec($ch);
$codeHttp = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
        if($codeHttp != 200){
return $response;
}
else{
return false;
}
}
catch(Exception $e){
return $e;
}
}

Controller

Criei um controller chamado FrontendController. O código em si apenas chamará um evento (do Laravel para o NodeJS) que será escutado e executado pelo Laravel, enviando uma requisição para o Express no NodeJS que disparará um evento (do NodeJS para os Clientes) para todos os clientes conectados no servidor.

Dentro do arquivo criei uma função simples. No topo do arquivo não esqueça de fazer o use. E não esqueça de criar a rota para essa função.

Route::post('like', 'FrontendController@like');

Veja a função abaixo.

use App\Events\LikeEvent;
public function like(Request $request){
$params = [
'name' => $request->get('name'),
'message' => $request->get('name'). ' curtiu sua publicação'
];
   // FIRE EVENT
event(new LikeEvent($params, 'like'));
}

Então, quando eu clicar no botão CURTIR a função acima será executada que disparará um evento. O evento será escutado pelos listeners que executará uma função que enviará uma requisição via CURL para o Express do NodeJS.

Respira um pouco!

3… 2… 1… ! E o Express disparará um evento para os clientes conectados e aí você poderá fazer alguma graça. Nesse caso coloquei um popup para notificar o usuário.

Resultado.

Conclusão

Foi isso o que eu consegui sem o Redis. Eu gostei bastante do resultado.

É verdade, são dois servidores (PHP e NodeJS), mas foi a única forma no momento que eu achei para integrar essas tecnologias. Mas para facilitar eu sempre coloco a regra do negócio no PHP. O NodeJS é apenas utilizado para emitir eventos para os clientes.

Espero que gostem desse artigo e seja útil para a comunidade.
E se houver dúvidas responderei o mais rápido possível.

Tive também a ajuda do Programador Web e de Games Matheus Crivellari.