Artigo original escrito por: Arun Mathew Kurian
Artigo original: How to build a real time chat application in Node.js using Express, Mongoose and Socket.io
Traduzido e adaptado por: Daniel Rosa

Neste tutorial, usaremos a plataforma do Node.js para criar uma aplicação de bate-papo em tempo real que envia e exibe mensagens a um destinatário instantaneamente, sem precisar atualizar a página. Usaremos o framework Express.js do  JavaScript e as bibliotecas Mongoose e Socket.io para conseguir isso.

Antes de começarmos, vamos dar uma rápida olhada no básico a respeito do Node.js

Node.js

O Node.js é um ambiente de tempo de execução do JavaScript, de código aberto e multiplataforma, que executa código em JavaScript fora do navegador. A vantagem mais importante de usar o Node é podermos usar o JavaScript tanto como linguagem de front-end quanto de back-end.

Como já sabermos, o JavaScript foi usado primordialmente como linguagem de scripts do lado do client, onde os scripts eram integrados no HTML de uma página da web e executados por um mecanismo de JavaScript no navegador da web do usuário.

O Node.js permite que os desenvolvedores usem o JavaScript para escrever ferramentas de linha de comando e scripts para o lado do servidor — executando os scripts do lado do servidor de modo a produzir conteúdo dinâmico para a página da web antes mesmo de a página ser enviada ao navegador da web do usuário.

Para instalar o node, acesse:

https://nodejs.org/en/download/

Embora o Node seja de thread única, ele ainda é mais rápido para o uso de funções assíncronas. O Node pode processar outras coisas enquanto um arquivo estiver sendo lido no disco ou enquanto aguarda que uma solicitação de HTTP seja concluída. O comportamento assíncrono pode ser implementado usando funções de callback. Além disso, o JavaScript funciona bem com o JSON e com bancos de dados No-SQL.

Módulos do NPM

O Node.js permite que os módulos de bibliotecas sejam incluídos na aplicação. Esses módulos podem ser definidos pelo usuário ou de terceiros.

Os módulos de terceiros podem ser instalados usando o seguinte comando:

npm install nome_do_módulo

e os módulos instalados podem ser usados por meio da função require():

var module = require('nome_do_módulo')

Nas aplicações do Node, usaremos um arquivo package.json para manter as versões dos módulos. Esse arquivo pode ser criado através deste comando:

npm init

Os pacotes, por sua vez, podem ser instalados assim:

npm install -s nome_do_módulo

Existem muitos frameworks que podem ser adicionados como módulos em nossa aplicação do Node. Eles serão explicados mais adiante, conforme for necessário.

Aplicação de bate-papo simples

A aplicação deve permitir que diversos usuários possam bater papo juntos. As mensagens devem ser atualizadas sem que seja preciso atualizar a página. Para fins de simplicidade, desconsideraremos a parte da autenticação.

Podemos começar criando um diretório para o projeto e navegando para ele. Em seguida, podemos iniciar nosso projeto usando o seguinte comando:

npm init

Isso nos levará a inserir detalhes sobre o projeto.

Depois disso, será criado um arquivo package.json:

{
 "name": "test",
 "version": "1.0.0",
 "description": "",
 "main": "index.js",
 "scripts": {
 "test": "echo \"Error: no test specified\" && exit 1"
 },
 "author": "",
 "license": "ISC"
}

Nosso diretório da aplicação agora está pronto.

A primeira coisa que precisamos criar é um servidor. Para criá-lo, faremos uso de um framework chamado Express.

Express.js

O Express.js, ou, simplesmente, Express, é um framework para aplicações da web para o Node.js. O Express fornece um conjunto bastante abrangente de recursos para aplicações para a web e dispositivos móveis (em inglês). O Express fornece uma camada fina de recursos de aplicações para a web fundamentais, sem obscurecer os recursos do próprio Node.js.

Instalaremos o Express.js usando o seguinte comando:

npm install -s express

Dentro do arquivo package.json, será adicionada uma linha nova:

"dependencies": {
 "express": "4.16.3"
 }

Em seguida, criaremos um arquivo server.js.

Nesse arquivo, precisaremos solicitar o Express e criar uma referência a uma variável de instância do Express. O conteúdo estático, como o HTML, o CSS ou o JavaScript pode ser servido usando o express.js:

var express = require('express');

var app = express();

Também podemos começar a "escutar" em uma porta usando o código abaixo:

var server = app.listen(3000, () => {
 console.log('server is running on port', server.address().port);
});

Agora, precisamos criar um arquivo HTML chamado index.html, que exibe nossa interface de usuário (UI). Adicionei aqui o CDN para o Bootstrap e para o JQuery .

//index.html

<!DOCTYPE html>
<html>
<head>
 <! — incluir os CDNs do Bootstrap e do JQuery →
</head>
<body>
<div class="container">
 <br>
 <div class="jumbotron">
 <h1 class="display-4">Send Message</h1>
 <br>
 <input id = "name" class="form-control" placeholder="Name">
 <br>
 <textarea id = "message" class="form-control" placeholder="Your Message Here">
</textarea>
 <br>
 <button id="send" class="btn btn-success">Send</button>
 </div>
 <div id="messages">
 
</div>
</div>
<script>

</script>
</body>
</html>

Observe que a tag <script> </script> vazia será o local onde escreveremos o código em JavaScript do lado do client.

Para informar ao Express que usaremos um arquivo estático, adicionaremos uma nova linha em server.js:

app.use(express.static(__dirname));

Podemos executar server.js usando o comando

node ./server.js

ou um pacote chamado nodemon, de modo que as alterações feitas no código sejam detectadas automaticamente. Faremos o download do nodemon usando o comando

npm install -g nodemon

A flag -g significa 'global', e serve para que o nodemon esteja acessível em todos os projetos.

Executaremos o código usando o comando

nodemon ./server.js

Se você for para localhost:3000 no navegador, verá o arquivo index.html sendo exibido:

caxmtV7tYzJ1EUU69TeX4YQVsC69EhgzcSL5
index.html

Agora que nosso servidor está em execução, precisamos criar nosso banco de dados. Para esta aplicação, usaremos um banco de dados No-SQL e usaremos o Mongodb. Estou configurando meu mongodb em MongoDB Atlas. O banco de dados terá uma única coleção, chamada messages, com os campos name e message.

Para conectarmos esse banco de dados à aplicação, usaremos outro pacote, chamado Mongoose.

Mongoose

O Mongoose é uma ferramenta de modelagem de objetos do MongoDB projetada para funcionar em um ambiente assíncrono. O Mongoose pode ser instalado usando o comando

npm install -s mongoose

Dentro de server.js, precisamos solicitar o mongoose:

var mongoose = require('mongoose');

Então, atribuiremos a uma variável o URL de nosso banco de dados:

var dbUrl = 'mongodb+srv://username:password@ds257981.mongodb.net:57981/simple-chat'

O Mongoose se conectará com o bando de dados por meio do método connect:

mongoose.connect(dbUrl , (err) => { 
   console.log('mongodb connected',err);
})

E definiremos nosso modelo de mensagem assim:

var Message = mongoose.model('Message',{ name : String, message : String})

Podemos implementar a lógica de bate-papo agora. Antes disso, no entanto, precisamos fazer alguns ajustes ao arquivo server.js.

Logo abaixo de var app = express();, adicione o código a seguir ao arquivo:

app.use(express.json());
app.use(express.urlencoded({extended: false}))

Roteamento

O roteamento se refere a como os endpoints de uma aplicação (URIs) respondem às solicitações do client. Você define o roteamento usando métodos do objeto da aplicação do Express que correspondem a métodos HTTP: app.get() para lidar com as solicitações GET e app.post() para lidar com as solicitações POST.

Esses métodos de roteamento especificam uma função de callback (texto em inglês), a qual às vezes também recebe o nome de "função de tratamento", chamada quando a aplicação recebe uma solicitação para a rota (endpoint) especificada e o método HTTP. Em outras palavras, a aplicação "escuta" as solicitações que correspondem às rotas e aos métodos especificados. Quando uma correspondência é detectada, ela chama a função de callback especificada.

Agora, precisamos criar duas rotas para as mensagens para que nosso bate-papo funcione.

No server.js:

get: receberá todas as mensagens do banco de dados

app.get('/messages', (req, res) => {
  Message.find({},(err, messages)=> {
    res.send(messages);
  })
})

post: publicará novas mensagens criadas pelo usuário no banco de dados

app.post('/messages', (req, res) => {
  var message = new Message(req.body);
  message.save((err) =>{
    if(err)
      sendStatus(500);
    res.sendStatus(200);
  })
})

Para se conectar com essas rotas ao front-end, precisamos adicionar o código abaixo na tag script do lado do client no index.html:

$(() => {
    $("#send").click(()=>{
       sendMessage({
          name: $("#name").val(), 
          message:$("#message").val()});
        })
      getMessages()
    })
    
function addMessages(message){
   $("#messages").append(`
      <h4> ${message.name} </h4>
      <p>  ${message.message} </p>`)
   }
   
function getMessages(){
  $.get('http://localhost:3000/messages', (data) => {
   data.forEach(addMessages);
   })
 }
 
function sendMessage(message){
   $.post('http://localhost:3000/messages', message)
 }

Aqui, sendMessage é usada para invocar a rota de post das mensagens e salvar uma mensagem enviada pelo usuário. A mensagem é criada quando o usuário clica no botão Send.

Da mesma forma, getMessage é usada para invocar a rota de get das mensagens. Ela obterá todas as mensagens salvas no banco de dados e será incluída na div de mensagens.

m1tJ6aV53XnmvkU8PjY7u16wkI1gKrplYWHo

A única questão agora é que não há como o client saber se o servidor foi atualizado. Assim, a cada vez que publicarmos uma mensagem, precisaremos atualizar a página para ver as novas mensagens.

Para resolver isso, podemos adicionar uma sistema de notificações push que enviará mensagens do servidor para o client. No Node.js, usamos o socket.io.

Socket.io

O Socket.IO é uma biblioteca do JavaScript para aplicações da web em tempo real. Essa biblioteca permite a comunicação em tempo real e bidirecional entre clients da web e um servidor (texto em inglês). Ela tem duas partes: uma biblioteca para o client, executada no navegador, e uma biblioteca para o servidor, para o Node.js. O Socket.io permite a comunicação bidirecional baseada em eventos e em tempo real.

Para instalar o socket.io:

npm install -s socket.io

também precisamos do pacote HTTP para que o Socket.io funcione:

npm install -s http

Adicione o código a seguir ao arquivo server.js:

var http = require(‘http’).Server(app);
var io = require(‘socket.io’)(http);

Logo, criamos uma conexão:

io.on('connection', () =>{
 console.log('a user is connected')
})

No index.html, adicione a tag abaixo:

<script src="/socket.io/socket.io.js"></script>

Agora, precisamos criar uma ação de emissão no server.js para quando uma mensagem é criada. A rota de post, portanto, fica assim:

app.post('/messages', (req, res) => {
  var message = new Message(req.body);
  message.save((err) =>{
    if(err)
      sendStatus(500);
    io.emit('message', req.body);
    res.sendStatus(200);
  })
})

Para a tag de script do lado do client, por sua vez, adicione o seguinte código ao arquivo index.html:

var socket = io();

socket.on('message', addMessages)

Desse modo, cada vez que uma mensagem for publicada, o servidor atualizará as mensagens na div de mensagens.

6KUYtaL4L3ShtPNaHRKWXvP6v3mMuUAdq6R0

Pronto!

Essa é uma aplicação bastante básica que podemos criar no Node.js. Há lugar para fazermos muitas melhorias. O código final pode ser encontrado em https://github.com/amkurian/simple-chat

server.js

var express = require('express');
var app = express();
var http = require('http').Server(app);
var io = require('socket.io')(http);
var mongoose = require('mongoose');

app.use(express.static(__dirname));
app.use(express.json());
app.use(express.urlencoded({extended: false}))

var Message = mongoose.model('Message',{
  name : String,
  message : String
})

var dbUrl = 'mongodb+srv://username:password@ds257981.mongodb.net:57981/simple-chat'

app.get('/messages', (req, res) => {
  Message.find({},(err, messages)=> {
    res.send(messages);
  })
})

app.get('/messages', (req, res) => {
  Message.find({},(err, messages)=> {
    res.send(messages);
  })
})

app.post('/messages', (req, res) => {
  var message = new Message(req.body);
  message.save((err) =>{
    if(err)
      sendStatus(500);
    io.emit('message', req.body);
    res.sendStatus(200);
  })
})

io.on('connection', () =>{
  console.log('a user is connected')
})

mongoose.connect(dbUrl ,{useMongoClient : true} ,(err) => {
  console.log('mongodb connected',err);
})

var server = http.listen(3001, () => {
  console.log('server is running on port', server.address().port);
});

Espero que este artigo tenha sido útil no entendimento de alguns conceitos básicos.

Alguns links úteis (em inglês):

Socket.IO
Express
Mongoose