Você está na página 1de 143

Aplicações Realtime com Node.js e Socket.

io
Leonan Lupi
Esse livro está à venda em http://leanpub.com/chat-com-node-js-e-socket-io

Essa versão foi publicada em 2018-10-05

Esse é um livro Leanpub. A Leanpub dá poderes aos autores e editores a partir do processo de
Publicação Lean. Publicação Lean é a ação de publicar um ebook em desenvolvimento com
ferramentas leves e muitas iterações para conseguir feedbacks dos leitores, pivotar até que você
tenha o livro ideal e então conseguir tração.

© 2017 - 2018 Leonan Lupi


Conteúdo

Introdução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1

Criando Ambiente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Criando o ambiente no windows . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Criando o ambiente no linux . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
Criando o ambiente no MAC OSX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6

Principais Tecnologias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
BackEnd . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
FrontEnd . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12

APIs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14

Configurações iniciais do projeto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21


Introdução . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
Iniciando Projeto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
Setando servidor e configurações . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 24
Setando frontend e configurações . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29

Criando Área Administrativa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39

Criando Administração de Usuário . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43


Definição de schema inicial - User . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
CRUD - User . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
Criando serviço edit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
Criando serviço remove . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56

Criando Administração de Salas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58


Definição de schema inicial - Room . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
CRUD - Room . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59

Criando Login e Logout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69


Criando login . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72

FrontEnd . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
Iniciando Projeto FrontEnd . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
CONTEÚDO

Configurando gulp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
Marcando aside - chatbar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
Marcando section - chatbox . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
Estilizando aside - chatbar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
Estilizando section - chatbox . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94

BackEnd . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
Configurando Express . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98
Criando web API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
Criando Servidor Socket . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106

Criando Comunicação Via Socket . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108


Enviando Mensagens . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
Recebendo Mensagens . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111

Comunicação em Grupo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113


Liberando CORS . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
Mostrando Grupos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
Mensagens em Grupo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118

Finalizando a Chatbar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132


Mostrando usuários . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
Mensagem por Usuário . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133

Conclusão . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
Introdução
Este ebook terá como objetivo ensinar a criar uma plataforma de chat utilizando as tecnologias
Node.js e Socket.io.
Nesta introdução mostraremos o que desenvolveremos e como faremos.
Nos primeiros capítulos apresentaremos os conceitos técnicos para aplicarmos da melhor forma.
Ensinaremos a montar o ambiente e passaremos alguns conceitos básicos como: conceitos de API,
como trabalhar com as APIs e mostraremos alguns padrões de retorno como json.
Além de mostrarmos o padrão de retorno, apresentaremos a linguagem e como se deu seu
surgimento.
Explicaremos sobre a arquitetura que utilizaremos e conforme avançarmos, comentaremos porque
estamos utilizando determinada arquitetura e outras informações que forem importantes.
Tudo que desenvolvermos, faremos uma introdução conceitual, antes de começarmos a praticar.
No decorrer dos capítulos comentaremos sobre cada tecnologia utilizada.
Ao final vocês terão um conjunto de informações importantes, de modo que consigam desenvolver
uma aplicação que seja realmente funcional em produção.

As principais tecnologias utilizadas

Linguagens e Frameworks Protocolos e Serviços


Node.js HTTP
Express.js TCP
Socket.io API
Html Websocket
CSS
Javascript
MongoDB

Observem que trabalharemos com diferentes tecnologias, em uma mesma aplicação.


Como vocês podem ver, temos muito conteúdo pela frente e, com certeza, trará muito conhecimento
sobre todas as tecnologias citadas.
Criando Ambiente
Nesse capítulo, você aprenderá como criar o ambiente de desenvolvimento nas três principais
plataformas: Windows, Linux e Mac OS.
Antes de iniciarmos, é muito importante que você esteja com o ambiente configurado da forma
correta e esteja alinhado com o mesmo ambiente que utilizaremos para o desenvolvimento do
conteúdo, para que não haja erro durante o processo.

Criando o ambiente no windows

Instalando Noje.js
Você deverá instalar o Node.js para acompanhar o projeto.

• Acesse o site https://nodejs.org/en/download/


• Baixe o Windows Installer

Quando baixar o Node.js, a ferramenta gerenciadora de pacotes do Node, que se chama npm, é
instalada. Fique atento ao tipo de arquitetura do sistema operacional que você está utilizando, se é
32 ou 64 bits.
Há duas versões: LTS e Current. A escolha da versão a ser usada, deve ser feita a partir dos seguintes
parâmetros:
Utilize a versão LTS, que está no mercado há mais tempo e que já possui uma quantidade maior de
usuários, bem como bibliotecas que utilizam esta versão.
A versão current é a que possui as novas features da linguagem e todas as atualizações/melhorias
da plataforma, porem conta com uma quantidade menor de usuarios utilizando pelo fato de ser a
versão mais nova.
Ambas poderam ser utilizadas durante o processo de produção de nosso chat. Atenção apenas a
versão do Node.js que deverá ser maior que 4.0 para que tudo funcione perfeitamente.
Após o download, faça a instalação, aceite os termos, escolha o local da instalação e finalize o
processo.
Para confirmar se está tudo funcionando, abra o terminal e rode os comandos: nodejs -v ou node
-v e npm -v.
Criando Ambiente 3

nodejs -v
ou
node -v

npm -v

As versões deverão ser retornadas. Se obtiver este resultado, está tudo instalado e configurado.
Como terminal, sugerimos o uso do Git Bash ou do Cmder. Você também poderá utilizar o Powershell
ou CMD, que são nativos do Windows.

Instalando MongoDB
Para a instalação do MongoDB, acesse o site abaixo:
https://www.mongodb.com/

• Clique no menu Solutions


• Acesse a seção Try It Now
• Clique em Download Center.

Dentro de download center, clique na plataforma Windows e efetue o download.


Na data deste conteúdo, a versão disponível é a Windows Server 2008. Pode instalar nas versões 7,
8 ou 10 do Windows, que funcionará.
Após o download, aceite os termos, escolha a opção Complete para instalar tudo que for necessário,
escolha o local da instalação e finalize o processo.
O MongoDB não tem inicialização automática. Para ativá-lo você deverá rodar os comandos abaixo
no terminal:

// Acessando a pasta do executável


cd Program Files\MongoDB\Server\3.2\bin\

// Rodando executável pela primeira vez


mongod --dbpath=/whatever/data/path
// Rodando executável após primeira vez
mongod
Criando Ambiente 4

Não esqueça de dar start no serviço sempre que precisar utilizar. Note também a presença do
parâmetro dbpath que é necessário quando startamos o MongoDB pela primeira vez. Esse parâmetro
tem como objetivo dizer ao MongoDB onde deverá armazenar os arquivos gerados pela criação de
banco de dados, coleções e outros e só deve ser utilizado na primeira vez que startar o serviço do
MongoDB.

Criando o ambiente no linux


Para configurar o ambiente de desenvolvimento na plataforma Linux, utilizamos como base o
Sistema Operacional Ubuntu. Você pode utilizar qualquer outro que tenha o Debian como base.
O primeiro passo é acessar o terminal como root. Para isso abra o terminal e rode os comandos
abaixo:

sudo su
apt-get update
apt-get upgrade

Para instalar um software dentro do Linux existem muitas formas:

• Baixar os binários e compilar, que é o processo indicado no site


• Baixar de um repositório
• Utilizar o apt-get.

Instalando Node.js e Npm


Utilizaremos o comando apt-get. Veja o comando abaixo:

apt-get install nodejs

Rodando este comando, você só precisará confirmar a instalação.


Após a instalação do Node.js, você deverá instalar o gerenciador de pacotes npm, que não é instalado
com o Node, na plataforma Linux.
Criando Ambiente 5

apt-get install npm

Ao utilizar o Linux, você precisará instalar outro pacote para evitar alguns erros de compilação de
alguns pacotes que utilizará durante o desenvolvimento.
Instale o pacote Build Essential:

apt-get install build-essentials

O comando executável do Node.js no Linux será nodejs. Se precisar rodar um script ou executar
algum programa com node, você precisará rodar o comando nodejs no terminal.
Para saber as versões, rode:

nodejs -v
ou
node -v

npm -v

Caso seja necessário, atualize a versão do Node e do npm, para que possa acompanhar o conteúdo,
utilizando as mesmas versões.
Rode o comando abaixo

npm install -g n
sudo n stable

Baixe as últimas versões(LTS) disponíveis.

Instalando MongoDB
Acesse o site abaixo e siga o passo a passo para a sua distribuição.
https://docs.mongodb.com/manual/tutorial/install-mongodb-on-ubuntu/
Criando Ambiente 6

Rode os comandos abaixo na mesma sequência:

sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 0C49F3730359A14518\


585931BC711F9BA15703C6

echo "deb [ arch=amd64,arm64 ] http://repo.mongodb.org/apt/ubuntu xenial/mongodb-org\


/3.4 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-3.4.list

sudo apt-get update

sudo apt-get install -y mongodb-org

Estes comandos serão responsáveis pela instalação do MongoDB, bem como outras ferramentas que
são utilizadas entre o Mongo e o sistema operacional.
Sempre que for utilizar o serviço do MongoDB, você terá que iniciar o serviço, rodando o comando
abaixo:

service mongod start

Criando o ambiente no MAC OSX


O MAC, ao contrário do Ubuntu e outras plataformas Linux, não possui nenhum gerenciador de
pacote nativo. Ele possui uma ferramenta chamada Homebrew.
O Homebrew é o gerenciador de pacote mais utilizado dentro do ambiente OSX. Você deverá instalá-
lo.
Acesse o site https://brew.sh/.
Rode o seguinte comando:

/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/ma\


ster/install)"

É importante instalar utilizando o comando sudo, para instalar como administrador.


O comando brew passa a ser para o MAC o que o apt-get é para o Linux.
Criando Ambiente 7

Instalando Node.js e Npm

brew install node

Aceitando a instalação, já terá o node instalado.


Para não ter problemas na hora de compilar alguns arquivos, instale o XCODE. O xcode se assemelha
ao Build Essential.
Para instalação do XCODE acesse a Apple Store e pesquise por xcode. Se não estiver instalado, efetue
a instalação pela própria Apple Store.
Após a instalação, acesse o terminal e rode o seguinte comando:

xcode-select install

Com este comando você verifica se o sistema foi instalado. Se houver algum problema, haverá uma
mensagem informando.
Atualizando o Node e o Npm, para a última versão:

npm install n -g
sudo n stable

Para conferir as versões e saber se está tudo funcionando corretamente rode os seguintes comandos
no terminal:

node -v

npm -v

MongoDB
Criando Ambiente 8

brew install mongodb

Aceite os termos e terá tudo que é necessário para rodar o MongoDB.


Sempre que for utilizar o serviço do MongoDB você deverá iniciá-lo, rodando um dos comandos
abaixo:

service mongod start

brew services start mongodb

Para este projeto indicamos o uso do Sublime Text ou Visual Studio Code, para codificar.
Mas esteja livre para escolher o editor de sua preferência.
Principais Tecnologias
Neste capítulo apresentaremos a você as principais tecnologias que serão utilizadas no backend e
frontend da aplicação:

• Node.js
• Express.js
• MongoDB
• SASS
• Javascript

BackEnd

Node.js

Node.js é um interpretador de código JavaScript, que funciona do lado do servidor. Seu


objetivo é ajudar programadores na criação de aplicações de alta escalabilidade (como
um servidor web), com códigos capazes de manipular dezenas de milhares de conexões
simultâneas, numa única máquina física. - Wikipédia

O Node possui duas características muito importantes para o projeto:

1. Permite a criação de aplicações, em tempo real


2. É altamente escalável.

Esta tecnologia consegue manipular milhares de conexões, simultâneas, em uma mesma máquina.
É o que você necessita para o desenvolvimento do chat. Lógico que, se o chat for muito grande, você
deve reavaliar se apenas uma máquina será suficiente, mas comparado à outras linguagens, o Node
está muito à frente em relação a esta capacidade.
Com outras linguagens, você perceberá que não terá a mesma facilidade em trabalhar em tempo real
e para escalar o projeto.
Se comparar o Java com o Node, você verá como é mais fácil trabalhar com o Node e perceberá que
não será necessário escalar de forma contínua o projeto como um todo. Claro que toda aplicação
deve ser analisada, pontualmente.
Em linhas gerais, o Node tem mais recursos.
Para testar o Node, siga os passos abaixo:
Principais Tecnologias 10

• Crie uma pasta para o projeto, pode chamá-la de chatschool


• Abra o terminal e acesse a pasta raiz
• Rode o comando:

npm init -y

Após rodar o comando, você obterá um arquivo chamado package.json na pasta raiz.
Conteúdo do arquivo package.json

1 {
2 "name": "chat",
3 "version": "1.0.0",
4 "description": "",
5 "main": "index.js",
6 "scripts": {
7 "test": "echo \"Error: no test specified\" && exit 1"
8 },
9 "author": "",
10 "license": "ISC"
11 }

Após a obtenção deste arquivo, crie um outro arquivo chamado index.js, na pasta raiz do projeto.
Veja o conteúdo deste arquivo:

1 var http = require('http')


2
3 http.createServer((req,res) => {
4 res.writeHead(200, { 'Content-Type': 'text/plain'})
5 res.end('Hello World')
6 })
7 .listen(3000)
8
9 console.log("Server started")

Depois de adicionar este código ao arquivo index.js, execute o arquivo com o comando abaixo:
Principais Tecnologias 11

node index.js

Após rodar o comando, caso não tenha nenhum erro, você terá acesso a aplicação através do endereço
http://localhost:3000, em seu browser.
Se obtiver como resultado o Hello World, está tudo correto com o ambiente e com o Node.

Express.js
O Express.js é um framework web rápido, flexível e minimalista, para Node.js. Ele é o principal
framework de mercado e o mais utilizado em ambiente de produção.
Acesse o link: http://expressjs.com/pt-br/starter/installing.html
Para instalar este framework, rode os comandos abaixo, no terminal:

npm install express --save

Concluída a instalação, abra o arquivo index.js novamente e comente todo código existente. Faremos
o mesmo processo anterior, porém utilizando o Express.

1 var express = require('express')


2 var app = express()
3
4 app.get('/',(req,res) => {
5 res.send("Hello World")
6 })
7
8 app.listen(3000, () => {
9 console.log("Server started")
10 })

Assim, você terá o mesmo servidor funcionando na porta 3000. Execute o arquivo com o node e
acesse novamente o endereço abaixo, no browser:
http://localhost:3000
O Express permitiu criar e manipular um servidor web com poucas linhas de código. O mais
importante é que você pode criar rotas facilmente com este framework, pois ele disponibiliza este
recurso nativamente.
Principais Tecnologias 12

Utilizaremos o Express para criar uma API.


O Express disponibiliza formas de você agregar um template engine e outros pacotes, o que faz dele
um framework completo e plugável.

MongoDB

MongoDB é uma aplicação de código aberto, de alta performance, sem esquemas, orientado
a documentos. Foi escrito na linguagem de programação C++. Além de orientado a
documentos, é formado por um conjunto de documentos JSON. Muitas aplicações podem,
dessa forma, modelar informações de modo muito mais natural, pois os dados podem ser
aninhados em hierarquias complexas e continuar a ser indexáveis e fáceis de buscar. -
Wikipédia

Além da definição acima, o MongoDB se caracteriza por:

• Alta escalabilidade
• Muita velocidade

O MongoDB será a infra-estrutura para guardar informações. Logo, você trabalhará com o Express
para receber e manipular as informações e, em seguida as gravará no MongoDB.
Para este procedimento você utilizará o mongoose.
http://mongoosejs.com/
Esta ferramenta tem como principal característica ajudar a manipular o MongoDB, diretamente do
Node.js. Desta forma, você conseguirá fazer praticamente tudo utilizando a mongoose, com bastante
abstração.
Com o mongoose você conseguirá:

• Abrir e fechar conexões


• Criar schemas, que são os objetos do banco de dados
• Manipular dados
• Gravar dados no banco

FrontEnd

SASS
Atualmente, está em alta a utilização de pré-processadores. Trata-se de uma linguagem por trás do
CSS, como:
Principais Tecnologias 13

SASS e LESS

Cada um tem sua característica própria, vantagens e desvantagens, como qualquer outra ferramenta.
O principal intuito destes pré-processadores, é tornar o trabalho com o CSS mais organizado e
estruturado.
Com estes pré-processadores você pode:

• Trabalhar com variáveis


• Funções
• Mixins
• Gerenciar arquivos de uma maneira mais fácil

A princípio, você utilizará apenas o CSS. No decorrer do desenvolvimento, se houver necessidade,


você poderá utilizar algum pré-processador.

Javascript
O Javascript tem uma característica muito útil que é trabalhar de forma assíncrona. Isso significa
que você não precisa esperar uma requisição terminar para começar outra. Além disso, você pode
implementá-lo de várias formas, como:

• Paradígma estrutural
• Orientação a objeto, que é o mais utilizado, atualmente
• Implementar na forma funcional

Com todas estas informações, conclui-se tratar de uma linguagem muito versátil e com a ajuda
do HTML, ele consegue implementar várias APIs existentes, como: websocket e notification, por
exemplo.
APIs
Nesse capítulo você entenderá melhor sobre o que são APIs, a relação que elas possuem com JSON
e também sobre Websockets.
API significa Application Program Interface, ou seja, ela fornece uma interface para que sotwares
internos ou externos consigam consumir seus serviços. Um exemplo muito simples de API pode ser
dado sobre uma consulta de CEP no sites dos Correios. Basicamente, você acessa uma URL (que nesse
caso pode ser chamada de endpoint) onde passando o CEP desejado, em um formato já especificado,
você receberá as informações de um endereço. Perceba que nesse caso, o software que está realizando
a consulta do cep está consumindo um serviço.
Um ponto importante a ser considerado, é a relação que muitas pessoas fazem entre API e JSON
(JavaScript Object Notation), ou seja, acabam confundindo que JSON é a própria API. JSON é apenas
o formato de dados de como as informações que serão disponibilizadas, ou seja, posso ter uma API
que retorna um XML ou até mesmo texto puro sem nenhuma formatação.
Exemplo de XML

<menu id="file" value="File">


<popup>
<menuitem value="New" onclick="CreateNewDoc()" />
<menuitem value="Open" onclick="OpenDoc()" />
<menuitem value="Close" onclick="CloseDoc()" />
</popup>
</menu>

Exemplo de JSON

{"menu": {
"id": "file",
"value": "File",
"popup": {
"menuitem": [
{"value": "New", "onclick": "CreateNewDoc()"},
{"value": "Open", "onclick": "OpenDoc()"},
{"value": "Close", "onclick": "CloseDoc()"}
]
}
}}
APIs 15

Por motivos óbvios, podemos sempre optar por deixar uma API pública ou privada. No caso dela ser
privada, provavelmente será necessário enviarmos credenciais corretas no momento da requisição.
APIs não foram criadas apenas para fornecer consultas de informações, mas também para realizar
transações como:

• consultar dados
• adicionar
• editar
• remover

Um dos protocolos mais utilizados para Web APIs é o protocolo HTTP, utilizando REST.
Vale lembrar que o framework Express, tem total suporte a JSON e a APIs.

Veja a alteração na rota criada no capítulo anterior. Ao invés de adicionar a mensagem Hello World
será adicionado um menu no formato JSON:

var express = require('express')


var app = express()

app.get('/',(req,res) => {
res.send("Hello World")
})

app.get('/',(req,res) => {
res.json({
menu: {
id: 1,
value: "1",
popup:{

}
}
})
})

app.listen(3000, () => {
console.log("Server started")
})

Para checar o exemplo acima:


APIs 16

• rode o comando node index.js


• acesse a url http://localhost:3000/
• o objeto menu, no formato JSON, será exibido no navegador.

WebSockets
WebSocket tem o objetivo de permitir a comunicação bidirecional (envie e recebe mensagens) entre
canais através de TCP/IP. Ele não faz parte especificamente do HTML 5, porém, é um protocolo
padronizado pela IETF, bem como pela W3C. Basicamente, uma conexão persistente é criada entre
o cliente e o servidor, e ambas as partes podem enviar e receber dados a qualquer momento.
O sistema de URL é, totalmente, diferente do HTTP. No protocolo http a url começa com http ou
https para conexões seguras, já no protocolo websocket, a url começa com ws.
Para requisições não criptografadas utiliza-se ws e, para criptografadas wss.
Todos os navegadores convencionais, inclusive o Internet Explorer, a partir da versão 10, já suportam
trabalhar com Websockets, nativamente.
Implementaremos um exemplo prático, para que você consiga entender melhor como funciona o
processo em tempo real e websockets.

• Crie um novo arquivo chamado index.html, na pasta raiz de exemplo.

1 <!DOCTYPE html>
2 <html lang="en">
3 <head>
4 <meta charset="UTF-8">
5 <title>Title</title>
6 </head>
7 <body>
8 <script>
9 console.log('Hello World')
10 </script>
11 </body>
12 </html>

Abra o terminal para instalar o http-server.

npm install http-server -g

Rode o seguinte comando, dentro da pasta raiz:


APIs 17

http-server

Com o servidor rodando, o próprio comando retornará o endereço de acesso.

Starting up http-server, serving ./


Available on:
http://127.0.0.1:8080
http://192.168.0.9:8080
http://192.168.0.8:8080

Observe que podemos acessar em http://localhost:8080/


Para verificar o funcionamento do arquivo index.html:

• Abra developer tool


• Inspecione o console
• Hello World será mostrado

Tendo este recurso, já podemos começar a trabalhar com Websockets. Não vamos instalar nenhuma
biblioteca. Utilizaremos apenas a API padrão que já vem no HTML5 e vamos consumir um servidor
Websocket público e que é, somente, para teste.
Com base nisso, vamos receber e exibir mensagens.

<script>
(function() {
var URI = 'ws://echo.websocket.org/'
var websocket = null
var init = function() {
websocket = new WebSocket(URI)
websocket.onopen = function(event) {
onOpen(event)
}
}
function onOpen(e) {
console.log('Connected')
sendMessage('This is my first message from WS')
}
APIs 18

function sendMessage(str) {
websocket.send(str)
}
init()
})()
</script>

Primeiro, criamos uma função auto-executável e definimos a URI com endereço do servidor
websocket para teste e a variável websocket, como nula.
Em seguida, atribuímos uma função para a variável init. Nesta função instanciamos o Websocket e
definimos que ele executasse a função onOpen no evento de onOpen. A função onOpen executa um
console.log e envia uma mensagem para o servidor, através da função sendMessage que definimos,
logo abaixo.
A função sendMessage utiliza o método send, para enviar a mensagem que passamos como
parâmetro. Quando acessar o server, na porta 8080 e inspecionar o console, o log será mostrado
e se acessar a aba network, na seção WS, poderá ver a mensagem enviada para o servidor.
Para completar o processo, podemos adicionar o evento onMessage para receber a mensagem, em
tempo real.

<script>
(function() {
var URI = 'ws://echo.websocket.org/'
var websocket = null
var init = function() {
websocket = new WebSocket(URI)
websocket.onopen = function(event) {
onOpen(event)
}
websocket.onmessage = function(event) {
onMessage(event)
}
}
function onOpen(e) {
console.log('Connected')
sendMessage('This is my first message from WS')
}
function sendMessage(str) {
websocket.send(str)
}
function onMessage(event) {
console.log('received')
APIs 19

console.log(event.data)
}
init()
})()
</script>

Após a criação da função sendMessage, adicionamos o evento na função init e depois criamos a
função onMessage.
Abra o navegador e inspecione, novamente. Terá a mensagem sendo enviada em tempo real no
console.
Existem outras ações, pré-definidas, para serem utilizadas. Assim como temos a ação onMessage e
onOpen, temos onClosed. Basicamente, criamos a ação e depois definimos a função, para atender
determinada ação.

1 <!DOCTYPE html>
2 <html lang="en">
3 <head>
4 <meta charset="UTF-8">
5 <title>Document</title>
6 </head>
7 <body>
8 <div id="wrapper">
9 <ul id="messages">
10 </ul>
11 </div>
12 <script>
13 (function() {
14 var URI = 'ws://echo.websocket.org/'
15 var websocket = null
16 var init = function() {
17 websocket = new WebSocket(URI)
18 websocket.onopen = function(event) {
19 onOpen(event)
20 }
21 websocket.onmessage = function(event) {
22 onMessage(event)
23 }
24 websocket.onclose = function(event) {
25 onClose(event)
26 }
27 }
28 function onOpen(e) {
APIs 20

29 console.log('Connected')
30 sendMessage('This is my first message from WS')
31 }
32 function sendMessage(str) {
33 websocket.send(str)
34 }
35 function onMessage(event) {
36 console.log('Reciveid')
37 console.log(event.data)
38 var msg = document.getElementById('messages')
39 var item = document.createElement('li')
40 item.innerHTML = event.data
41 msg.appendChild(item)
42 websocket.close()
43 }
44 function onClose(event) {
45 console.log('Disconnected')
46 }
47 init()
48 })()
49 </script>
50 </body>
51 </html>

Para ver toda mudança funcionando não esqueça de parar o servidor e subir novamente com o
comando http-server.
Adicionamos a ação onclose e depois definimos a função onClose, que simplesmente faz um log
no console. Observe que adicionamos uma tag ul do HTML, para receber a mensagem quando ela é
enviada. Resgatamos o elemento ul e depois, com o javascript, criamos um elemento li e adicionamos
a mensagem enviada pelo websocket. Tudo isso, antes de fecharmos a conexão.
Observe que mostramos que podemos trabalhar com Websockets e com javascript em conjunto com
o HTML, de forma simples.
Com este exemplo, criamos a conexão, enviamos uma mensagem, recebemos esta mensagem e
imprimimos na tela, como se fosse um processo completo do chat, porém muito simples e manual,
somente para exemplificar.
Configurações iniciais do projeto
Introdução
A partir de agora, começaremos a trabalhar na prática.
O próximo passo será desenvolver o ambiente de administração do projeto. Cadastraremos algumas
configurações que serão utilizadas futuramente, no chat.
Criaremos uma estrutura muito simples, procurando utilizar tudo o que possuímos, sempre de forma
simples.
Utilizaremos:

• Handlebars
• Express
• MongoDB

Além dos recursos citados acima, utilizaremos todos os recursos que citamos, anteriormente. Nos
próximos capítulos utilizaremos estes dados, acessando-os através de uma API para o frontend.

Iniciando Projeto
Para iniciar o projeto, crie uma pasta com o nome do projeto e, dentro da pasta, rode o comando de
inicialização do npm. Chamamos o projeto de chatschool.

npm init -y

Após preencher os dados do comando inicial, o arquivo package.json será criado.


Configurações iniciais do projeto 22

1 {
2 "name": "chatschool",
3 "version": "1.0.0",
4 "description": "chatschool",
5 "main": "app.js",
6 "scripts": {
7 "test": "echo \"Error: no test specified\" && exit 1"
8 },
9 "keywords": [
10 "chat",
11 "socket.io",
12 "express"
13 ],
14 "author": "",
15 "license": "ISC"
16 }

Com este arquivo criado, você começará a trabalhar com as dependências do projeto.
Veja a lista de ferramentas que deverá instalar para facilitar o desenvolvimento da aplicação:

Tabela de dependências do projeto

Comando Descrição
npm install express --save Framework node.js
npm install body-parser --save Trasnforma um objeto json para o formato javascript
npm install express-hbs --save Template engine que utilizará no projeto
npm install morgan --save Executa logs de toda requisição http, no console
npm install mongoose --save Permite trabalhar com banco de dados MongoDB
npm install express-validator --save Valida os dados da requisição
npm install express-session --save Cria seções para a aplicação
npm install method-override --save Permite o uso de verbos HTTPs que o navegador não dá
suporte.

Observe que, além de instalar, você está salvando as dependências no arquivo package.json,
utilizando o parâmetro –save.
Após a instalação de todas as dependências, o ambiente está pronto para o iniciar o projeto.
A página de admin será bem simples e utilizaremos o protocolo HTTP para fazer a autenticação. O
express será utilizado para auxiliar na manipulação dos dados transmitidos através da requisição.
Através das requisições a aplicação será direcionada para pontos específicos. Através deste procedi-
Configurações iniciais do projeto 23

mento você trabalhará com:

• riação
• edição
• remoção de dados na área administrativa.

Os dados configurados na área administrativa serão utilizados, posteriormente, para configurar o


chat.

Criando arquitetura inicial


Na raiz do projeto crie duas pastas:

• public
• src.

A pasta public será utilizada para armazenar arquivos estáticos e terá acesso direto pelo usuário.
Veja a estrutura da pasta public:

• chatschool
– public
* images
* js
* scss

A pasta src abrigará toda lógica da aplicação e terá a seguinte estrutura interna:

• chatschool
– src
* configs
* schemas
* routes
* services
* views
Configurações iniciais do projeto 24

Explicando estrutura

Pasta Descrição
configs Configurações da aplicação
routes Rotas de entrada ou endpoints para os usuários
schemas Gerenciador de schemas para cada projeto
services Onde estará toda lógica da aplicação
views Onde estarão todas visualizações, ou frontend, do projeto

Crie o arquivo principal, na pasta raiz chamado app.js.


Como pode ver, de acordo com a estrutura temos um projeto que será, relativamente simples, mas
muito eficiente no propósito que queremos atingir.
Uma observação importante a ser feita neste momento é que, podemos criar projetos implementando
apenas o que for realmente importante para ele. Muitos desenvolvedores adicionam diversas
funcionalidades ou bibliotecas que não tem muita necessidade, apenas para deixar o projeto mais
completo ou mais bonito. Estamos fazendo este alerta para que você pense muito, antes de adicionar
uma funcionalidade ou biblioteca que não seja necessária. Assim, terá um projeto simples, de fácil
manutenção e com uma performance melhor.
Como o projeto nesta fase será somente a criação de uma área administrativa, não precisaremos
utilizar bibliotecas mirabolantes para este fim.
Estamos visando tecnologias simples, porém eficientes, para que tenhamos o melhor da aplicação,
sem erros, com funcionalidade e velocidade.
Para finalizar a estrutura, crie duas pastas dentro da pasta views:

• partials
• layouts.

Dentro da pasta partials teremos os templates específicos para cada setor e dentro de layouts teremos
o código padrão de todos os templates.
Dentro da pasta routes teremos outras subpastas e arquivos que serão seguidas na pasta services,
mas criaremos conforme a necessidade dos conteúdos seguintes.

Setando servidor e configurações


Setaremos o servidor e, em seguida, iniciaremos o processo de configuração.
Iniciando com Express:
Configurações iniciais do projeto 25

1 const express = require('express')


2
3 const app = express()

Mesmo tendo o servidor inicializado, é necessário informar as configurações de algum ambiente.


Para isso, crie a estrutura dos ambientes do projeto.
Crie uma pasta, chamada env, dentro da pasta configs. Dentro desta pasta crie dois arquivos:
development.js e production.js.
Conteúdo de development.js:

1 const path = require('path')


2 const morgan = require('morgan')
3 const methodOverride = require('method-override')
4 const expressSession = require('express-session')
5 const bodyParser = require('body-parser')
6 const expressValidator = require('express-validator')
7 const hbs = require('express-hbs')
8
9 module.exports = (app) => {
10 app.set('port', 9000)
11 app.set('views', path.join(__dirname, './../../../build/views'))
12 app.set('view engine', 'hbs')
13
14 app.use(morgan('dev'))
15 app.use(bodyParser.json())
16 app.use(bodyParser.urlencoded({ extended: false }))
17 app.use(methodOverride('_method'))
18 app.use(expressSession({
19 secret: 'DJA!*@#(!#FDKJSHKJKJH!(#(',
20 resave: false,
21 saveUninitialized: false
22 }))
23 app.use(expressValidator())
24
25 app.engine('hbs', hbs.express4({
26 defaultLayout: path.join(app.get('views'), 'layouts/main.hbs'),
27 partialsDir: path.join(app.get('views'), 'partials'),
28 layoutsDir: path.join(app.get('views'), 'layouts')
29 }))
30 }
Configurações iniciais do projeto 26

Explicando estrutura

Configuração Descrição
app.set(‘port’, 9000) Define a porta de desenvolvimento
app.set(‘views’, path.join(…)) Configura o caminho das views
app.set(‘view engine’, ‘hbs’) Define qual será a template engine utilizada
app.use(morgan(‘dev’)) Configura o uso da ferramenta morgan
app.use(bodyParser.json()) Configura o uso da ferramenta bodyParse e a
linguagem utilizada
app.use(bodyParser.urlencoded({ extend: false })) Configura o uso da ferramenta bodyParse utilizando
urlencoded com extensão false
app.use(methodOverride(‘_method’)) Define o método override via query string
app.use(expressSession({…}) Configura os dados de sessão
app.use(expressValidator()) Configura as validações
app.engine(‘hbs’, hbs.express4({…}) Define template padrão e estrutura de pastas para
layouts

Após criar este arquivo com as configurações de desenvolvimento, inicie a aplicação passando estes
dados através do module.exports.
Veja o código do arquivo app.js, que é o arquivo de inicialização do projeto:

1 const express = require('express')


2 const path = require('path')
3
4 const app = express()
5
6 const env = path.join(__dirname, './src/configs/env', process.env.NODE_ENV || 'devel\
7 opment')
8
9 require(env)(app)
10
11 app.listen(app.get('port'), () => {
12 console.log('server')
13 })
Configurações iniciais do projeto 27

Setamos as constantes

Constante Descrição
express recebe a biblioteca do Framework
path recebe o caminho atual do arquivo
app recebe a inicialização de uma instância do express
env busca pela constante NODE_ENV, caso não encontre, determina o ambiente como
development

Um detalhe importante que deve ser considerado é o fato de termos a possibilidade de criarmos
ambientes diferentes. Atualmente, existe a possibilidade de termos três ambientes: teste, desenvol-
vimento e produção. Estamos separando cada configuração de ambiente em um arquivo diferente,
para que possamos trabalhar de forma mais organizada.
Depois, basta fazer o carregamento do ambiente e passar a instância app, para que possa iniciar a
aplicação.

1 require(env)(app)

Caso rode o arquivo app.js, no terminal, já terá o servidor rodando e a palavra server, como resposta
no terminal.

node app.js

E, por último, você pode configurar um endpoint para poder acessar no browser e ter uma mensagem
de confirmação de que o servidor já está funcionando e podendo ser acessado pela porta 9000.

1 const express = require('express')


2 const path = require('path')
3
4 const app = express()
5
6 const env = path.join(__dirname, './src/configs/env', process.env.NODE_ENV || 'devel\
7 opment')
8
9 require(env)(app)
10
11 app.listen(app.get('port'), () => {
12 console.log('server')
13 })
Configurações iniciais do projeto 28

14
15 app.get('/', (req, res) => {
16 res.send("School of Net")
17 })

Para ter certeza do funcionamento, basta executar o arquivo app.js, novamente, e acessar http://localhost:9000,
no navegador, para ter a aplicação rodando.

Criando estrutura correta e mais organizada


Em primeiro lugar, substitua o endpoint pelo seguinte código.

app.get('/', (req, res) => {


res.send("School of Net")
})

require('./src')(app)

Crie um arquivo chamado index.js, dentro da pasta src. Veja o conteúdo do arquivo index.js:

1 module.exports = (app) => {


2 app.use('/', require('./routes/main'))
3 }

Desta forma, você está dizendo que, quando o endpoint for acessado, irá requerer o arquivo index.js
da pasta routes/main, que ainda não criou. Crie este arquivo e adicione o seguinte código:

1 const express = require('express')


2 const router = express.Router();
3
4 router.get('/', require('./../../services/main'))
5
6 module.exports = router

Assim, você está atribuindo valores para as constantes: express e router. Para a constante express
carregamos o framework e para a constante router, atribuímos uma instância express.Router.
Depois, basta configurar a requisição do endpoint, para chamar um serviço que está na pasta
services/main/index.js. Este arquivo não foi criado, você deverá fazê-lo agora. Veja o conteúdo
deste arquivo:
Configurações iniciais do projeto 29

1 module.exports = (req, res) => {


2 res.send('School of Net')
3 }

Finalmente, temos o mesmo resultado anterior, porém de forma estruturada e organizada. Este é o
formato que utilizaremos na aplicação.
Para não precisar ficar parando o servidor e subindo novamente, a cada atualização feita no
desenvolvimento, instalaremos o nodemon.

sudo npm install nodemon -g

Depois de instalar, ao invés de rodar node app.js, rode nodemon app.js.


Após rodar o arquivo app.js acesse novamente o endereço http://localhost:9000. Terá o mesmo
resultado.
Vale lembrar que instalamos o express-validator que deverá ser aplicado às requisições, se houver
necessidade.

Setando frontend e configurações


Agora, falaremos sobre a estrutura do frontend.
Utilizaremos o Gulp para otimizar algumas tarefas. O Gulp será responsável por gerar a pasta build
durante o desenvolvimento e, a dist quando estiver em produção.
Primeiro passo será criar o arquivo gulpfile.js na pasta raiz do projeto. Depois de criar o arquivo,
instale algumas dependências no projeto.

npm install gulp --save-dev

npm install gulp-nodemon gulp-notify gulp-livereload del gulp-util gulp-concat


gulp-plumber gulp-imagemin gulp-minify-css gulp-minify-html gulp-rev
gulp-rev-collector gulp-uglify gulp-sass gulp-changed node-neat --save-dev
Configurações iniciais do projeto 30

Todas estas instalações são importantes para que possa trabalhar de forma mais organizada e
profissional. Assim, você terá ferramentas que trabalharão por você em tarefas muito importantes
em um projeto, cada uma com sua atuação.

Trabalhando com gulpfile.js


O arquivo ficou grande mas, depois de configurado, temos um ganho muito grande de produtividade,
porque todas as tarefas estão automatizadas, sem que precisemos ficar preocupados com estas
estruturas. Assim, focaremos apenas em desenvolvimento.
Por isso é importante perder um tempo no início do projeto, para configurarmos estas tarefas com
o uso das ferramentas que instalamos.
Crie um arquivo, chamado gulpfile.js, na pasta raiz do projeto.

Descrevendo processo
Observe que, em primeiro lugar, carregamos todas as ferramentas que instalamos e atribuímos às
suas respectivas constantes.

1 const gulp = require('gulp')


2 const nodemon = require('gulp-nodemon')
3 const notify = require('gulp-notify')
4 const livereload = require('gulp-livereload')
5 const changed = require('gulp-changed')
6 const del = require('del')
7 const gutil = require('gulp-util')
8 const concat = require('gulp-concat')
9 const plumber = require('gulp-plumber')
10 const imagemin = require('gulp-imagemin')
11 const minifyCss = require('gulp-minify-css')
12 const minifyHtml = require('gulp-minify-html')
13 const rev = require('gulp-rev')
14 const revCollector = require('gulp-rev-collector')
15 const uglify = require('gulp-uglify')
16 const sass = require('gulp-sass')

Em seguida, definimos uma constante chamada path, com todas os caminhos relativos que
precisaremos para configurar nossas tasks.
Configurações iniciais do projeto 31

1 const paths = {
2 fontsSrc: 'public/fonts/',
3 htmlSrc: 'src/views/',
4 sassSrc: 'public/scss/',
5 jsSrc: 'public/js/',
6 imgSrc: 'public/images/',
7
8 buildDir: 'build/',
9 revDir: 'build/rev/',
10 distDir: 'dist/'
11 }

Definimos uma variável chamada onError, para capturar todos os erros do processo. Desta forma,
acessaremos os erros e mostraremos, sempre que for necessário:

1 let onError = (err) => {


2 gutil.beep()
3 gutil.log(gutil.colors.red(err))
4 }

Utilizamos a biblioteca gulp-util para capturar os erros.

Em seguida, criamos uma task para iniciar a aplicação.

1 let initServer = () => {


2 livereload.listen()
3 nodemon({
4 script: 'app.js',
5 ext: 'js'
6 })
7 .on('restart', () => {
8 gulp.src('app.js')
9 .pipe(livereload())
10 .pipe(notify('Reloading...'))
11 })
12 }
Configurações iniciais do projeto 32

Atribuímos à variável initServer uma função que executa o script app.js utilizando a biblioteca
nodemon. O arquivo app.js é o arquivo inicial que estávamos rodando manualmente no terminal.
Configuramos a extensão (ext) no nodemon, para que a tarefa não confunda com algum outro
arquivo que possa ter o mesmo nome.
Configuramos o evento on restart, para que execute o arquivo, disparando um livereload e uma
notificação.

O próximo passo é configurar todas as tarefas relacionadas à criação dos recursos do frontend:

1 gulp.task('build-html', () => {
2 return gulp
3 .src(paths.htmlSrc.concat('**/*.hbs'))
4 .pipe(gulp.dest(paths.buildDir.concat('/views')))
5 .pipe(livereload())
6 })
7
8 gulp.task('build-css', () => {
9 return gulp
10 .src(paths.sassSrc.concat('**/*.scss'))
11 .pipe(sass({
12 includePaths: require('node-neat').includePaths,
13 style: 'nested',
14 onError: function(){
15 console.log('SASS ERROR!')
16 }
17 }))
18 .pipe(plumber({ errorHandler: onError }))
19 .pipe(gulp.dest(paths.buildDir.concat('/css')))
20 .pipe(livereload())
21 })
22
23 gulp.task('build-js', () => {
24 return gulp
25 .src(paths.jsSrc.concat('*.js'))
26 .pipe(plumber({ errorHandler: onError }))
27 .pipe(changed(paths.buildDir.concat('/js')))
28 .pipe(gulp.dest(paths.buildDir.concat('/js')))
29 .pipe(livereload())
30 })
31
Configurações iniciais do projeto 33

32 gulp.task('build-images', () => {
33 return gulp
34 .src(paths.imgSrc.concat('**/*.+(png|jpeg|gif|jpg|svg)'))
35 .pipe(changed(paths.buildDir.concat('/images')))
36 .pipe(gulp.dest(paths.buildDir.concat('/images')))
37 .pipe(livereload())
38 })
39
40 gulp.task('build-fonts', () => {
41 return gulp
42 .src(paths.fontsSrc.concat('**/*.*'))
43 .pipe(gulp.dest(paths.buildDir.concat('/fonts')))
44 .pipe(livereload())
45 })

Repare que, criamos os nomes das tarefas de uma forma muito intuitiva. Criamos as seguintes tarefas:

• build-html
• build-css
• build-js
• build-images
• build-fonts.

Agora, você pode desenvolver os códigos css, html, js, images e fontes, que o gulp compilará tudo
para você em uma pasta chamada build. Qualquer alteração que fizer será assistida pelo gulp e ele
se encarregará de compilar tudo, da melhor maneira possível, para o projeto.
Depois de criar as tarefas, você deve executá-las de forma automática.
Primeiro, crie uma tarefa chamada build que executa todas as tarefas anteriores. Veja a tarefa:

1 gulp.task('build', ['build-html', 'build-css', 'build-js', 'build-images', 'build-fo\


2 nts'], (done) => {
3 return initServer()
4 })

Depois de executar todas as tarefas, ele executa a função initServer para reiniciar a aplicação com
as alterações já compiladas pelo gulp.
Ainda não executamos a tarefa build, para que todo este processo seja executado. Para isso, criaremos
uma outra tarefa chamada watch e depois, executamos a tarefa build e watch.
Tarefa watch:
Configurações iniciais do projeto 34

1 gulp.task('watch', () => {
2 gulp.watch(['src/views/**/*.hbs'], ['build-html']);
3 gulp.watch('public/scss/**', ['build-css']);
4 gulp.watch(paths.jsSrc + '**/*.js', ['js']);
5 gulp.watch(paths.imgSrc + '**/*.+(png|jpeg|jpg|svg)', ['build-images']);
6 })

Criadas as tarefas build e watch, basta configurar a variável de ambiente em uma constante env.
Configuramos esta variável para que possamos executar tarefas diferentes, em diferentes ambientes.
Por enquanto temos só o ambiente development sendo testado, no futuro poderemos criar tarefas
para outros ambientes.
Finalmente, vem a execução de todos os processos automatizados, quando executamos a tarefa
default do gulp, no terminal:

1 const env = process.env.NODE_ENV || 'development'


2
3 if (env === 'development') {
4 return gulp.task('default', ['build', 'watch'])
5 }

Executando a tarefa no terminal:

gulp default

Ou somente:

gulp

Após a execução desta tarefa, teremos todos os recursos compilados e o servidor rodando, da mesma
forma anterior. Caso tenha algum problema, com alguma biblioteca, você pode excluir a pasta
node_modules, atualizar o npm e instalar tudo novamente.

# Atualizando npm
npm install npm -g

# Instalando todas dependências


Configurações iniciais do projeto 35

npm install

A instalação de todas as bibliotecas é feita facilmente porque as salvamos como dependências no


arquivo package.json.

Criando frontend com estrutura final


Crie um arquivo, chamado main.hbs, na pasta src/views/layouts que será o layout padrão.

1 <!DOCTYPE html>
2 <html lang="en">
3 <head>
4 <meta charset="UTF-8">
5 <title>{{ title }}</title>
6 <meta name="viewport" content="width=device-width, initial-scale=1"/>
7 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-boot\
8 strap/3.3.7/css/bootstrap.min.css">
9 <link rel="stylesheet" href="/css/app.css">
10 </head>
11 <body>
12
13 {{> main/header }}
14
15 <main class="content-wrapper">
16 {{{ body }}}
17 </main>
18
19 {{> main/footer }}
20
21 <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/b\
22 ootstrap.min.js"></script>
23 <script src="/js/app.js"></script>
24 </body>
25 </html>

No layout padrão temos uma estrutura HTML padrão e já aproveitamos para trabalhar com variáveis
handlebars({{ title }}) e chamamos alguns templates partials ({{> main/header }} e {{> main/footer
}}).
Observe que já carregamos o CSS e o JS do Bootstrap, no layout padrão. Os links são do site: https:
//cdnjs.com/libraries/twitter-bootstrap/, onde você pode escolher qual versão utilizar.
Configurações iniciais do projeto 36

Carregamos o arquivo css/app.css e também o js/app.js. Estes arquivos ainda não foram criados.
Crie estes arquivos com os seguintes conteúdos:
Conteúdo public/scss/app.scss

1 body {
2 background: red;
3 }

Conteúdo public/js/app.js

1 (function () {
2 console.log('Its ok')
3 })

Crie dentro da pasta /views/partials, uma outra pasta chamada main, onde criaremos os layouts
parciais. Crie dois arquivos: header.hbs e footer.hbs.
Conteúdo header.hbs:

1 <h1>Header</h1>

Conteúdo footer.hbs:

1 <h1>Footer</h1>

Alterando chamada da view


Atualmente, no arquivo src/services/main/index.js, temos o serviço enviando uma mensagem para
o endpoint. Veja o código abaixo:

1 module.exports = (req, res) => {


2 res.send('School of Net')
3 }

Porém, com a nova estrutura, devemos fazer esta chamada de outra forma. Veja a maneira correta
e altere seu arquivo:
Configurações iniciais do projeto 37

1 module.exports = (req, res) => {


2 return res.render('main/index', {
3 title: 'Chatschool - Admin'
4 })
5 }

Precisamos criar a pasta src/views/main e o arquivo index.hbs, dentro da pasta views. Veja o
conteúdo do arquivo index.hbs:

1 <h1>School of Net</h1>

Notem que estamos tendo o mesmo resultado, mas agora sendo chamado um arquivo HTML e não
mais uma mensagem direta.
Com esta estrutura, estamos passando a informação title para que seja renderizada como handlebar.
Podemos enviar mais informações, caso seja necessário. Como estamos utilizando apenas a variável
title em nosso template, passamos apenas ela.
Após todas estas alterações e implementações, basta rodar no terminal a tarefa gulp, novamente.

gulp default

Ou somente:

gulp

Você poderá acessar o navegador e terá acesso às alterações. Ainda existe um erro que devemos
configurar, para que tenhamos todas as alterações compiladas.
No arquivo src/configs/env/development.js adicione o seguinte código:

1 const express = require('express')


2
3 app.set('assets', path.join(__dirname, './../../../build'))
4
5 app.use(express.static(app.get('assets')))

O código acima faz com que os assets sejam reconhecidos, fazendo com que as alterações de css e js
sejam aplicadas ao layout padrão.
Configurações iniciais do projeto 38

Temos as alterações e templates sendo renderizados de forma correta e automatizada.


Caso haja alguma dúvida ou erro, releia o conteúdo para que consiga entender, corrigir e fixar melhor
o conceito.
Lembre-se que esta configuração, apesar de ser grande e complexa, é muito importante para o
desenvolvimento e é feita apenas uma vez, no início do desenvolvimento.
Criando Área Administrativa
Criaremos a página principal da área administrativa.
Antes de começarmos, precisamos incluir no projeto a biblioteca jQuery, pois o Bootstrap utiliza
esta biblioteca para executar algumas funcionalidades.
Antes do carregamento do javascript do Bootstrap, adicionaremos no arquivo src/views/layouts/-
main.hbs.

1 <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></sc\
2 ript>
3 <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/boots\
4 trap.min.js"></script>
5 <script src="/js/app.js"></script>

O painel administrativo terá:

• um menu, em que o usuário poderá navegar


• uma seção, onde terão os formulários para cadastrar os dados necessários para a aplicação.

Para começarmos, editaremos o arquivo src/views/partials/main/header.hbs.

1 <header id="header">
2 <nav class="navbar navbar-inverse">
3 <div class="container-fluid">
4 <div class="nav-header">
5 <button type="button" class="navbar-toggle collapsed" data-toggle="c\
6 ollapse" data-target="#nav">
7 <span class="sr-only"></span>
8 <span class="icon-bar"></span>
9 <span class="icon-bar"></span>
10 <span class="icon-bar"></span>
11 </button>
12 <a href="/" class="navbar-brand">ChatSchool</a>
13 </div>
14 <div class="collapse navbar-collapse" id="nav">
15 <ul class="nav navbar-nav">
16 <li class="active">
Criando Área Administrativa 40

17 <a href="/">Home</a>
18 </li>
19 <li class="dropdown">
20 <a data-toggle="dropdown" class="dropdown-toggle" href="\
21 ">
22 Users
23 <span class="caret"></span>
24 </a>
25 <ul class="dropdown-menu">
26 <li>
27 <a href="/users">List</a>
28 <a href="/users/new">Create</a>
29 </li>
30 </ul>
31 </li>
32 <li class="dropdown">
33 <a data-toggle="dropdown" class="dropdown-toggle" href="\
34 ">
35 Rooms
36 <span class="caret"></span>
37 </a>
38 <ul class="dropdown-menu">
39 <li>
40 <a href="/rooms">List</a>
41 <a href="/rooms/new">Create</a>
42 </li>
43 </ul>
44 </li>
45 </ul>
46 </div>
47 </div>
48 </nav>
49 </header>

Depois de criar o header, inicie a construção do footer em src/views/partials/main/footer.hbs.


Criando Área Administrativa 41

1 <footer id="footer" class="footer-inverse">


2 <div class="row">
3 <div class="container text-center">
4 <p>Copyright &copy; ChatSchool 2017</p>
5 </div>
6 </div>
7 </footer>

Para seguir a mesma linha de estilização do header, adicione o seguinte código no arquivo
public/scss/app.scss:

1 body {
2 background: #fefefe;
3 }
4
5 #header {
6 .navbar {
7 border-radius: 0;
8 }
9 }
10
11 #footer {
12 &.footer-inverse {
13 background: #222222;
14 padding: 2rem;
15 p {
16 color: white;
17 }
18 }
19 }

Veja como ficou a estrutura completa do arquivo src/views/layouts/main.hbs:

1 <!DOCTYPE html>
2 <html lang="en">
3 <head>
4 <meta charset="UTF-8">
5 <title>{{ title }}</title>
6 <meta name="viewport" content="width=device-width, initial-scale=1"/>
7 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-boot\
8 strap/3.3.7/css/bootstrap.min.css">
9 <link rel="stylesheet" href="/css/app.css">
Criando Área Administrativa 42

10 </head>
11 <body>
12 {{> main/header }}
13
14 <main class="content-wrapper">
15 <div class="row">
16 <div class="container">
17 {{{ body }}}
18 </div>
19 </div>
20 </main>
21
22 {{> main/footer }}
23
24 <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js">\
25 </script>
26 <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/b\
27 ootstrap.min.js"></script>
28 <script src="/js/app.js"></script>
29 </body>
30 </html>

Se adicionar todos os elementos, você já terá um painel administrativo estruturado.


A partir de agora começaremos a implementar as funcionalidades.
Caso tenha alguma dúvida de Bootstrap, você pode ler a documentação ou procurar pelo conteúdo
de Bootstrap da School of Net.
Criando Administração de Usuário
Definição de schema inicial - User
Iniciaremos a criação dos usuários do sistema.
A partir de agora começaremos a trabalhar com o MongoDB para gravar dados do usuário no banco.
Inicie o serviço do mongodb, para que não tenha nenhum tipo de erro.
No Windows
Acesse a pasta binária da instalação do MongoDB (c:Program FilesMongoDBServer\3.2\bin) e rode
o comando abaixo:

mongod

Linux e Mac

service mongod start


ou
brew services start mongodb

Com o serviço ativo, criaremos o primeiro arquivo dentro da pasta schemas. Crie um arquivo
chamado users.js.

1 const mongoose = require('mongoose')


2
3 const User = new mongoose.Schema({
4 name: {
5 type: String,
6 required: true
7 },
8 slug: {
9 type: String,
10 required: true
Criando Administração de Usuário 44

11 },
12 email: {
13 type: String,
14 required: true
15 },
16 password: {
17 type: String,
18 required: true
19 }
20 })
21
22 module.exports = mongoose.model('User', User);

Observe que configuramos o schema com um objeto e passamos os dados presentes neste objeto.
Informamos o tipo e configuramos como campos obrigatórios, utilizando o required: true.
Depois, exportamos o módulo, para dar continuidade a implementação.
Para funcionar corretamente, temos que configurar o uso do Mongoose no arquivo src/configs/env/-
development.js.

1 const mongoose = require('mongoose')


2
3 module.exports = (app) => {
4
5 app.set('mongo_host','127.0.0.1')
6 app.set('mongo_port',27017)
7 app.set('mongo_db','chatschool_dev')
8 app.set('mongo_url',`mongodb://${app.get('mongo_host')}:${app.get('mongo_port')}/${a\
9 pp.get('mongo_db')}`)
10
11 // Final do arquivo
12 mongoose.connect(app.get('mongo_url'))
13
14 }

Após todas estas configurações, estamos prontos para começar a gravar dados no banco, porque já
temos o primeiro schema criado e a configuração de conexão com MongoDB.

CRUD - User
Criaremos rotas específicas para que o usuário consiga cadastrar novos usuários no banco. Siga os
seguintes passos:
Criando Administração de Usuário 45

• abra a pasta src/routes


• crie uma pasta chamada users
• crie um arquivo index.js

Veja o código deste arquivo:

1 const express = require('express')


2 const router = express.Router()
3
4 const createRules = require('./../validator/users/create')
5 const editRules = require('./../validator/users/edit')
6 const removeRules = require('./../validator/users/remove')
7 const updateRules = require('./../validator/users/update')
8
9 router.get('/' , require('./../../services/users/index'))
10 router.get('/new' , require('./../../services/users/new'))
11 router.get('/edit/:id' , editRules, require('./../../services/users/edit'))
12 router.get('/:id' , require('./../../services/users/show'))
13 router.post('/' , createRules, require('./../../services/users/create'))
14 router.put('/:id' , updateRules, require('./../../services/users/update'))
15 router.patch('/:id' , updateRules, require('./../../services/users/update'))
16 router.delete('/:id' , removeRules, require('./../../services/users/remove'))
17
18 module.exports = router

Desta forma, configuramos todos os possíveis pontos de entrada no painel administrativo. Depois
de configurar as rotas, crie os arquivos que servirão as mesmas.
Crie dentro da pasta src/services/users, os seguintes arquivos:

• index.js
• new.js
• edit.js
• show.js
• create.js
• update.js
• remove.js

Após a criação das rotas e serviços, criaremos as views. Crie dentro da pasta src/views/users

• index.hbs
• new.hbs
Criando Administração de Usuário 46

• edit.hbs
• show.hbs
• create.hbs
• update.hbs
• remove.hbs

Observe que estamos criando validações para os campos no arquivo de rotas para usuários, utilizando
a biblioteca de validações que instalamos com o gerenciador npm.

1 createRules = require('./../validator/users/create')
2 editRules = require('./../validator/users/edit')
3 removeRules = require('./../validator/users/remove')
4 updateRules = require('./../validator/users/update')

Para que estas validações funcionem, precisamos criar os arquivos citados acima. Crie uma pasta
dentro de rotas que serão os chamados middlewares, ou seja, eles farão uma validação antes de
terminar o processo completo. Caso haja algum problema, ele retorna este erro e não finaliza.
Dentro da pasta src/routes/validator/users, crie os arquivos:

• create.js
• edit.js
• remove.js
• update.js

Até o momento vimos as estruturas de arquivos e pastas. Chegou a hora de, finalmente, começarmos
a trabalhar com códigos e fazer todos estes serviços funcionarem.
Veja os códigos dos arquivos de validações:

1 // users/create
2 module.exports = (req, res, next) => {
3 req
4 .checkBody('name', 'Field name is required')
5 .notEmpty()
6 req
7 .checkBody('email', 'Field email is required')
8 .notEmpty()
9 .isEmail()
10 req
11 .checkBody('password', 'Field password is required')
12 .notEmpty()
13
Criando Administração de Usuário 47

14 let errors = req.validationErrors()


15
16 if (!errors) {
17 return next()
18 }
19
20 return res.redirect('/users/new')
21 }
22
23 // users/edit
24 module.exports = (req, res, next) => {
25 req
26 .checkParams('id', 'Field id is required')
27 .notEmpty()
28 .isMongoId()
29
30 let errors = req.validationErrors()
31
32 if (!errors) {
33 return next()
34 }
35
36 return res.redirect('/users')
37 }
38
39 // users/remove
40 module.exports = (req, res, next) => {
41 req
42 .checkParams('id', 'Field id is required')
43 .notEmpty()
44 .isMongoId()
45
46 let errors = req.validationErrors()
47
48 if (!errors) {
49 return next()
50 }
51
52 return res.redirect('/users')
53 }
54
55 // users/update
56 module.exports = (req, res, next) => {
Criando Administração de Usuário 48

57 req
58 .checkParams('id', 'Field id is required')
59 .notEmpty()
60 .isMongoId()
61
62 let errors = req.validationErrors()
63
64 if (!errors) {
65 return next()
66 }
67
68 return res.redirect('/users')
69 }

Observe que todas as validações possuem o parâmetro next, o que caracteriza ser um middleware,
ou seja, se não houver erro durante as validações, ele retorna para o processo da rota e conclui o
restante do processo.
Todas as validações receberam dados de requisição(res), response(res) e o next(next), para dar
continuidade. Por isso podemos tratar os dados durante a validação e prosseguir, normalmente,
o fluxo da lógica.
As validações verificam:

• se os parâmetros existem
• se estão vazios
• se são ids do MongoDB.

Desta maneira, estamos validando todos os campos e parâmetros da url que são passados.

Criando entrada geral


Já temos o endpoint configurado e todos os arquivos prontos para que sejam executados. Só falta
criarmos um link geral que será acessado pelo usuário. A partir deste link teremos acesso a todos os
outros.
Para isso, editaremos o arquivo src/index.js. Veja a configuração abaixo:

1 module.exports = (app) => {


2 app.use('/', require('./routes/main'))
3 app.use('/users', require('./routes/users'))
4 }
Criando Administração de Usuário 49

Agora que temos, praticamente, tudo configurado, começaremos com o serviço create.
Em resumo, o cliente acessará a url http://localhost:9000/users/new. Quando ele acessa este link o
sistema acessa o arquivo src/routes/users/index.js que, por sua vez, possui a rota /new registrada.
Esta rota acessa o serviço src/services/users/new.js.
Veja o código do arquivo src/services/users/new.js:

1 const Users = require('./../../schemas/users')


2
3 module.exports = (req, res) => {
4 let user = new Users();
5
6 return res.render('users/create', {
7 title: 'Users - Chatschool Admin',
8 user
9 })
10 }

Este serviço gera o objeto de usuário e redireciona o processo para a view src/views/users/cre-
ate.hbs, passando o título e o objeto como parâmetro.
Veja o código da view:

1 <div class="panel panel-default">


2 <div class="panel-heading">New user</div>
3 <div class="panel-body">
4 <form action="/users" method="POST">
5 {{> users/_form }}
6 </form>
7 </div>
8 </div>

Note que uma estrutura puxa a outra. Agora que criamos a view e chamamos uma partials,
precisamos criar este arquivo partials. Na pasta src/views/partials/users, criaremos o arquivo
_form.hbs.
Criando Administração de Usuário 50

1 <div class="form-group">
2 <label for="name">Name</label>
3 <input type="text" name="name" placeholder="Enter your name" class="form-control\
4 " value="{{ user.name }}">
5 </div>
6 <div class="form-group">
7 <label for="email">Email</label>
8 <input type="email" name="email" placeholder="Enter your email" class="form-cont\
9 rol" value="{{ user.email }}">
10 </div>
11 <div class="form-group">
12 <label for="password">Password</label>
13 <input type="password" name="password" placeholder="Enter your password" class="\
14 form-control" value="{{ user.password }}">
15 </div>
16 <div class="form-group">
17 <button type="submit" class="btn btn-primary">Save</button>
18 </div>

Veja que os atributos value são enviados pelo serviço através do objeto recebido, anteriormente.
Depois que o usuário preencher os dados do formulário e submeter, será enviada uma requisição do
tipo POST para a rota http://localhost:9000/users/, conforme a action do formulário nos indica.

<form action="/users" method="POST">

Desta forma, caimos, novamente, no arquivo src/routes/users/index.js que trata esta url e o tipo
da requisição, conforme código abaixo:

router.post('/', createRules, require('./../../services/users/create'))

Assim, concluímos que o serviço src/services/users/create.js é acionado e ele será encarregado de


cadastrar os dados no banco. Veja o código deste arquivo:
Criando Administração de Usuário 51

1 const Users = require('./../../schemas/users')


2
3 module.exports = (req, res) => {
4
5 req.body.slug = req.body.name.toLowerCase().replace(/ /g, '-')
6
7 Users
8 .create(req.body)
9 .then((user) => {
10 return res.redirect('/users')
11 })
12 .catch((error) => {
13 return res.send('Error ' + error)
14
15 })
16 }

Tratamos o slug para que tenha o formato esperado e, depois, utilizamos o objeto Users para efetuar
o cadastro no banco de dados. Caso tenhamos sucesso no cadastro, o processo é redirecionado para
a rota http://localhost:9000/users/ utilizando o método GET. Desta forma, teremos a listagem dos
usuários já cadastrados. Caso não tenhamos sucesso, o processo emite um erro para o usuário.
Como temos a rota mencionada acima, utilizando o método get, o processo identifica a seguinte rota
no arquivo de rotas:

router.get('/', require('./../../services/users/index'))

Assim, temos o serviço src/services/users/index sendo executado e terá o seguinte código:

1 const Users = require('./../../schemas/users')


2
3 module.exports = (req, res) => {
4 Users
5 .find()
6 .then((users) => {
7 return res.render('users/index', {
8 title: 'Users - ChatSchool Admin',
9 users
10 })
11 })
12 .catch((error) => {
Criando Administração de Usuário 52

13 return res.send('Error: ' + error)


14 })
15 }

Este serviço, por sua vez, pesquisa no banco de dados todos os usuários e se encontrar renderiza a
view src/views/users/index passando o título e uma coleção de usuários, como parâmetro. Caso
não encontre nenhum usuário, teremos um erro sendo mostrado.
Veja o código da view:

1 <div class="panel panel-default">


2 <div class="panel-heading">
3 List of users
4 </div>
5
6 <div class="panel-body">
7 <div class="table-responsive">
8 <table class="table table-bordered">
9 <thead>
10 <tr>
11 <th>Name</th>
12 <th>Email</th>
13 <th>Actions</th>
14 </tr>
15 </thead>
16
17 <tbody>
18 {{#each users}}
19 <tr>
20 <td>{{ this.name }}</td>
21 <td>{{ this.email }}</td>
22 <td>
23 <a href="/users/edit/{{ this._id }}" class="btn btn-info">
24 <i class="fa fa-pencil"></i>
25 </a>
26 <form method="POST" class="inline" action="/users/{{ this._i\
27 d }}?_method=DELETE">
28 <button type="submit" class="btn btn-danger">
29 <i class="fa fa-trash"></i>
30 </button>
31 </form>
32 </td>
33 </tr>
Criando Administração de Usuário 53

34 {{/each}}
35 </tbody>
36 </table>
37 </div>
38 </div>
39 </div>

Assim, finalizamos o processo de cadastro de usuário e a listagem dos mesmos. Você pode fazer o
teste no navegador. Não esqueça que é necessário rodar o gulp e o mongod no terminal, para não
ter nenhum erro.
Você pode encontrar um erro de layout porque no código acima adicionamos uma estilização para
os botões das actions.
Veja código abaixo:

1 <!-- Edit Button -->


2 <i class="fa fa-pencil"></i>
3 <!-- Delete Button -->
4 <i class="fa fa-trash"></i>

Esta estrutura refere-se a uma biblioteca de ícones que utilizaremos no projeto. Para que ela funcione,
precisamos carregá-la em nosso arquivo de layout principal src/views/layouts/main.hbs. Veja a
linha adicionada a este arquivo:

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstra\


p/3.3.7/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7\
.0/css/font-awesome.min.css">
<link rel="stylesheet" href="/css/app.css">

Adicione na tag head do HTML depois do css do Bootstrap e antes do nosso css app.scss.
Adicionamos uma estilização no arquivo public/scss/app.scss, também. Veja abaixo e adicione,
também:

1 .inline {
2 display: inline-block;
3 }

Este código serve para deixar os botões em uma mesma linha.


Criando Administração de Usuário 54

Falando sobre override method=”POST”


Anteriormente, instalamos uma biblioteca chamada method-override e não falamos dela ou de sua
utilização.
Agora, a utilizamos e queremos mostrar o pedaço do código e sua função.
Na parte de listagem de usuários que utilizamos o Bootstrap junto com a font awesome, para gerar
os botões de ações, utilizamos esta biblioteca. Veja o código abaixo:

1 <form method="POST" class="inline" action="/users/{{ this._id }}?_method=DELETE">


2 <button type="submit" class="btn btn-danger">
3 <i class="fa fa-trash"></i>
4 </button>
5 </form>

Veja que o protocolo HTTP só reconhece os métodos GET e POST. Porém, estamos fazendo com
que ele trabalhe como se fosse um DELETE. Esta é a função desta biblioteca.
Estamos sobrescrevendo o método e passando a query string.

/users/{{ this._id }}?_method=DELETE

Criando serviço edit


Já temos a estrutura de serviço de criação. Com isso, fica mais fácil entender e desenvolver o serviço
de edição.
Quando o usuário clicar no botão de edição, a rota redirecionará para o serviço que será processado
no arquivo src/services/users/edit.js. Veja o código abaixo:

1 const Users = require('./../../schemas/users')


2
3 module.exports = (req, res) => {
4
5 Users
6 .findById(req.params.id)
7 .then((user) => {
8 if (!user) {
9 return res.sendStatus(404)
Criando Administração de Usuário 55

10 }
11
12 return res.render('users/edit', {
13 title: 'Users - ChatSchool Admin',
14 user
15 })
16 })
17 .catch((error) => {
18 return res.send('Error : ' + error)
19 })
20 }

A lógica é procurar pelo id que é resgatado da URL. Se existir, ele direciona para a página de edição,
caso contrário é mostrado um erro.
Veja o código da view src/views/users/edit.hbs:

1 <div class="panel panel-default">


2 <div class="panel-heading">Edit user</div>
3 <div class="panel-body">
4 <form action="/users/{{ user._id }}?_method=PUT" method="POST">
5 {{> users/_form }}
6 </form>
7 </div>
8 </div>

O código é muito parecido com a view create. Note que utilizamos a biblioteca method-override
novamente, informando que o método será do tipo PUT.
Assim que o usuário atualizar os dados e clicar no botão de atualização, estará enviando uma
requisição para o serviço src/services/users/update.js. Veja o código:

1 const Users = require('./../../schemas/users')


2
3 module.exports = (req, res) => {
4 Users
5 .findByIdAndUpdate(req.params.id, req.body)
6 .then((user) => {
7 return res.redirect('/users')
8 })
9 .catch((error) => {
10 return res.send('Error: ' + error)
11 })
12 }
Criando Administração de Usuário 56

O serviço pesquisa pelo id e atualiza com os dados vindos da requisição, que já foram transformados
pela biblioteca body-parser. Caso a atualização tenha sucesso, ele redireciona para a página de
listagem de usuários. Caso contrário, informa um erro.

Criando serviço remove


Seguiremos a mesma linha dos demais serviços.
Como o botão delete, que já foi configurado, anteriormente, e está sendo direcionado para o serviço
de remoção, basta criarmos a ação de remoção neste serviço e retornar para a view de listagem de
usuários.
O método de remoção é sempre mais simples que os demais, porque não exige muita lógica. Veja o
código do arquivo src/services/users/remove.js, abaixo:

1 const Users = require('./../../schemas/users')


2
3 module.exports = (req, res) => {
4 Users
5 .findByIdAndRemove(req.params.id)
6 .then((user) => {
7 return res.redirect('/users')
8 })
9 .catch((error) => {
10 return res.send('Error: ' + error)
11 })
12 }

Temos o CRUD completo com: adição, edição e remoção de usuários na área administrativa.
Existe, apenas, um ponto a ser considerado. Em nosso arquivo de rotas, configuramos duas rotas que
executam o mesmo procedimento de atualização, porém estamos utilizando apenas uma.

1 router.put('/:id', updateRules, require('./../../services/users/update'))


2 router.patch('/:id', updateRules, require('./../../services/users/update'))

Veja que as duas executam o mesmo serviço, porém são acionadas por métodos HTTPs, diferentes.
Caso queiram utilizar o método patch, basta alterar o método na view edit.hbs:
Criando Administração de Usuário 57

1 <div class="panel panel-default">


2 <div class="panel-heading">Edit user</div>
3 <div class="panel-body">
4 <form action="/users/{{ user._id }}?_method=PATCH" method="POST">
5 {{> users/_form }}
6 </form>
7 </div>
8 </div>

O serviço não precisa ser alterado, somente o método, e você terá o mesmo resultado de atualização.
Utilizaremos o PUT, porque é mais comum.
Como criamos alguns arquivos de serviços que estão em branco e eles estão presentes em algumas
rotas que criamos você deve adicionar o conteúdo padrão dos serviços para que não tenha nenhum
erro na hora de rodar o comando gulp.
Adicione o código abaixo em todos os arquivos de serviços que estiverem em branco:

1 module.exports = (req, res) => {


2
3 }

Desta forma, você poderá rodar o comando não tendo nenhum erro. Ao rodar o comando faça testes
de cadastro, edição e exclusão de usuários.
Criando Administração de Salas
Definição de schema inicial - Room
Neste capítulo elaboraremos a estrutura para a criação de salas da plataforma de chat. Deixaremos
a estrutura de schema pronta para depois podermos cadastrar as salas, tranquilamente.
Dentro da pasta src/schemas, crie o arquivo room.js.

1 const mongoose = require('mongoose')


2
3 const Rooms = new mongoose.Schema({
4 name: {
5 type: String,
6 required: true
7 },
8 slug: {
9 type: String,
10 required: true
11 },
12 description: {
13 type: String,
14 required: true
15 },
16 enable: {
17 type: Boolean,
18 required: true,
19 default: true
20 },
21 users: [{
22 type: mongoose.Schema.Types.ObjectId,
23 ref: 'Users'
24 }],
25 created: {
26 type: Date,
27 required: true,
28 default: new Date()
29 }
30 })
Criando Administração de Salas 59

31
32 module.exports = mongoose.model('Rooms', Rooms)

Estrutura de campos para salas

Campo Tipo Descrição


name string Nome da sala
slug string Slug da sala
description string Descrição da sala
enable Boolean Saber se a sala está ativa ou não
users mongoose.Schema.Types.ObjectId Relacionado a tabela de usuários, para saber os usuários
ativos na sala
created Date Data de criação da sala

Com a estrutura de schema criada, desenvolveremos o sistema de cadastro de novas salas de chat.

CRUD - Room
Começaremos pela estrutura, uma vez que ela será parecida com a estrutura do CRUD de usuários.
Crie uma pasta chamada rooms, dentro das seguintes pastas:

• src/services
• src/views
• src/routes
• src/routes/validator

Dentro da pasta services, crie os seguintes arquivos:

• create.js
• edit.js
• index.js
• new.js
• update.js
• remove.js
• show.js

Dentro da pasta views, crie os seguintes arquivos:


Criando Administração de Salas 60

• create.hbs
• edit.hbs
• index.hbs

Dentro da pasta routes/rooms, crie o arquivo abaixo:

• index.js

Dentro da pasta routes/validator/rooms, crie os seguintes arquivos:

• create.js
• edit.js
• remove.js
• update.js

Para iniciarmos a estrutura, faremos da mesma forma que fizemos para o CRUD de users, apontando
a rota principal para o arquivo de rotas. Veja o código adicionado ao arquivo src/index.js:

1 module.exports = (app) => {


2 app.use('/', require('./routes/main'))
3 app.use('/users', require('./routes/users'))
4 app.use('/rooms', require('./routes/rooms'))
5 }

Depois de incluir este endpoint, acesse o arquivo src/routes/rooms/index.js e faça uma cópia do
arquivo de users, pois a base será a mesma:

1 const express = require('express')


2 const router = express.Router()
3
4 const createRules = require('./../validator/rooms/create')
5 const editRules = require('./../validator/rooms/edit')
6 const removeRules = require('./../validator/rooms/remove')
7 const updateRules = require('./../validator/rooms/update')
8
9 router.get('/', require('./../../services/rooms/index'))
10 router.get('/new', require('./../../services/rooms/new'))
11 router.get('/edit/:slug', editRules, require('./../../services/rooms/edit'))
12 router.get('/:id', require('./../../services/rooms/show'))
Criando Administração de Salas 61

13 router.post('/', createRules, require('./../../services/rooms/create'))


14 router.put('/:id', updateRules, require('./../../services/rooms/update'))
15 router.patch('/:id', updateRules, require('./../../services/rooms/update'))
16 router.delete('/:id', removeRules, require('./../../services/rooms/remove'))
17
18 module.exports = router

Note que assim como no arquivo de rotas de users, estamos aplicando as validações. Veja os códigos
dos arquivos de validações:

1 // validator - create.js
2 module.exports = (req, res, next) => {
3 req
4 .checkBody('name', 'Field name is required')
5 .notEmpty()
6 req
7 .checkBody('description', 'Field description is required')
8 .notEmpty()
9 req
10 .checkBody('enable', 'Field enable is required')
11 .notEmpty()
12
13 let errors = req.validationErrors()
14
15 if (!errors) {
16 return next()
17 }
18
19 return res.redirect('/rooms')
20 }
21
22 // validator - edit.js
23 module.exports = (req, res, next) => {
24 req
25 .checkParams('slug', 'Field slug is required')
26 .notEmpty()
27
28 let errors = req.validationErrors()
29
30 if (!errors) {
31 return next()
32 }
33
Criando Administração de Salas 62

34 return res.redirect('/rooms')
35 }
36
37 // validator - remove.js
38 module.exports = (req, res, next) => {
39 req
40 .checkParams('id', 'Field id is required')
41 .notEmpty()
42 .isMongoId()
43
44 let errors = req.validationErrors()
45
46 if (!errors) {
47 return next()
48 }
49
50 return res.redirect('/rooms')
51 }
52
53 // validator - update.js
54 module.exports = (req, res, next) => {
55 req
56 .checkParams('id', 'Field id is required')
57 .notEmpty()
58 .isMongoId()
59
60 let errors = req.validationErrors()
61
62 if (!errors) {
63 return next()
64 }
65
66 return res.redirect('/rooms')
67 }

Após a criação das rotas e aplicadas as validações, criaremos os serviços para o CRUD de rooms.
Veja os códigos de todos os serviços, abaixo:
Criando Administração de Salas 63

1 // services - create.js
2 const Rooms = require('./../../schemas/rooms')
3
4 module.exports = (req, res) => {
5 req.body.slug = req.body.name.toLowerCase().replace(/ /g, '-')
6 req.body.enable = req.body.enable ? true : false
7
8 Rooms
9 .create(req.body)
10 .then((room) => {
11 return res.redirect('/rooms')
12 })
13 .catch((error) => {
14 return res.send('Error: ' + error)
15 })
16 }
17
18 // services - edit.js
19 const Rooms = require('./../../schemas/rooms')
20
21 module.exports = (req, res) => {
22 Rooms
23 .findOne({
24 slug: req.params.slug
25 })
26 .then((room) => {
27 if (!room) {
28 return res.status(404).end()
29 }
30
31 return res.render('rooms/edit', {
32 title: 'Rooms - ChatSchool Admin',
33 room
34 })
35 })
36 .catch((error) => {
37 return res.send('Error: ' + error)
38
39 })
40 }
41
42 // services - index.js
43 const Rooms = require('./../../schemas/rooms')
Criando Administração de Salas 64

44
45 module.exports = (req, res) => {
46 Rooms
47 .find()
48 .then((rooms) => {
49 return res.render('rooms/index', {
50 title: 'Rooms - ChatSchool Admin',
51 rooms
52 })
53 })
54 .catch((error) => {
55 return res.send('Error: ' + error)
56 })
57 }
58
59 // services - new.js
60 const Rooms = require('./../../schemas/rooms')
61
62 module.exports = (req, res) => {
63 let room = new Rooms()
64
65 return res.render('rooms/create', {
66 title: 'Rooms - ChatSchool Admin',
67 room
68 })
69 }
70
71 // services - remove.js
72 const Rooms = require('./../../schemas/rooms')
73
74 module.exports = (req, res) => {
75 Rooms
76 .findByIdAndRemove(req.params.id)
77 .then((room) => {
78 return res.redirect('/rooms')
79 })
80 .catch((error) => {
81 return res.send('Error: ' + error)
82 })
83 }
84
85 // services - show.js
86 module.exports = (req, res) => {
Criando Administração de Salas 65

87 // Code here
88 }
89
90 // services - update.js
91 const Rooms = require('./../../schemas/rooms')
92
93 module.exports = (req, res) => {
94 req.body.slug = req.body.name.toLowerCase().replace(/ /g, '-')
95 req.body.enable = req.body.enable ? true : false
96
97 Rooms
98 .findByIdAndUpdate(req.params.id, req.body)
99 .then((room) => {
100 return res.redirect('/rooms')
101 })
102 .catch((error) => {
103 return res.send('Error: ' + error)
104 })
105 }

Uma vez que criamos todos os serviços e eles chamarão as views, vamos aos códigos de cada view:

1 <!-- views - create.hbs -->


2 <div class="panel panel-default">
3 <div class="panel-heading">New room</div>
4 <div class="panel-body">
5 <form action="/rooms" method="POST">
6 {{> rooms/_form }}
7 </form>
8 </div>
9 </div>
10
11 <!-- views - edit.hbs -->
12 <div class="panel panel-default">
13 <div class="panel-heading">Edit room</div>
14 <div class="panel-body">
15 <form action="/rooms/{{ room._id }}?_method=PUT" method="POST">
16 {{> rooms/_form }}
17 </form>
18 </div>
19 </div>
20
21 <!-- views - index.hbs -->
Criando Administração de Salas 66

22 <div class="panel panel-default">


23 <div class="panel-heading">
24 List of rooms
25 </div>
26
27 <div class="panel-body">
28 <div class="table-responsive">
29 <table class="table table-bordered">
30 <thead>
31 <tr>
32 <th>Name</th>
33 <th>Description</th>
34 <th>Actions</th>
35 </tr>
36 </thead>
37
38 <tbody>
39 {{#each rooms}}
40 <tr>
41 <td>{{ this.name }}</td>
42 <td>{{ this.description }}</td>
43 <td>
44 <a href="/rooms/edit/{{ this.slug }}" class="btn btn\
45 -info">
46 <i class="fa fa-pencil"></i>
47 </a>
48 <form method="POST" class="inline" action="/rooms/{{\
49 this._id }}?_method=DELETE">
50 <button type="submit" class="btn btn-danger">
51 <i class="fa fa-trash"></i>
52 </button>
53 </form>
54 </td>
55 </tr>
56 {{/each}}
57 </tbody>
58 </table>
59 </div>
60 </div>
61 </div>

Nestas views estamos utilizando, praticamente, a mesma estrutura das views de users. Trocamos
alguns campos e também o formulário. Você deve criar, dentro de src/views/partials/rooms, um
Criando Administração de Salas 67

arquivo chamado _form.hbs. Veja o conteúdo do formulário:

1 <div class="form-group">
2 <label for="name">Name</label>
3 <input type="text" name="name" placeholder="Enter room name" class="form-control\
4 " value="{{ room.name }}">
5 </div>
6
7 <div class="form-group">
8 <label for="description">Description</label>
9 <input type="text" name="description" placeholder="Enter room description" class\
10 ="form-control" value="{{ room.description }}">
11 </div>
12
13 <div class="form-group">
14 <label for="enable">Situação</label>
15 <input type="checkbox" name="enable" value="{{ room.enable }}" />
16 <label for="enable">
17 Ativo/Desativo?
18 </label>
19 </div>
20 <div class="form-group">
21 <button type="submit" class="btn btn-primary">Save</button>
22 </div>

Observe que o CRUD está pronto, só precisamos fazer uma adaptação.


Quando marcamos uma sala como ativa, ela consta no banco de dados como ativa, mas se clicar em
editar, você verá que o campo não vem marcado como ativo. Desta forma, não temos como saber se
está ou não ativada.
Para isso, crie uma pasta chamada helpers dentro da pasta src/configs. Utilizaremos um recurso do
template engine hbs.
Para configurarmos este helper no template engine, adicione o seguinte código no arquivo src/con-
figs/development.js:

1 mongoose.connect(app.get('mongo_url'))
2
3 require('./../helpers')(hbs)

Note que a primeira linha já existia no arquivo, o que adicionamos foi a segunda linha de código.
Nesta linha, como não passamos nenhum nome de arquivo específico, estamos carregando o arquivo
index.js, da pasta helpers, mas ainda não o criamos. Crie o arquivo index.js, dentro da pasta helpers
e adicione o seguinte código:
Criando Administração de Salas 68

1 module.exports = (hbs) => {


2 hbs.registerHelper('checkedIf', require('./checkedIf'))
3 }

Observe que registramos um helper chamado checkedIf, que provém do arquivo checkedIf.js, que
ainda não criamos. Este arquivo deve ser criado na pasta helpers e deve conter o seguinte código:

1 module.exports = (cond) => {


2 return cond ? 'checked' : ''
3 }

Desta forma, você pode criar diversos outros helpers e depois registrar no arquivo index.js.
Agora, basta utilizar o serviço na view src/views/partials/rooms/_form.hbs. Veja o código alterado
e aplicado:

1 <div class="form-group">
2 <label for="enable">Situação</label>
3 <input type="checkbox" name="enable" value="{{ room.enable }}" {{checkedIf room.\
4 enable}} />
5 <label for="enable">
6 Ativo/Desativo?
7 </label>
8 </div>

Estamos passando como parâmetro o valor da handlebar room.enable e o helper se encarregará de


retornar se a sala está ativa ou inativa.
Criando Login e Logout
A maioria dos sistemas possui uma área administrativa e, consequentemente, um sistema de
autenticação com login e senha.
Para criarmos o sistema de autenticação, utilizaremos duas bibliotecas que ainda não instalamos.
No terminal, instale as seguintes bibliotecas:

npm install passport passport-local-mongoose --save

Desenvolveremos a autenticação utilizando sessões, de uma maneira muito simples e rápida. Por
este motivo, instalamos a biblioteca express-session, pois o passport-local-mongoose é totalmente
dependente desta biblioteca.
A configuração será feita no arquivo src/configs/env/development.js. Veja o código adicionado a
este arquivo:

1 // Adicionar junto com as demais constantes


2 const passport = require('passport')
3 const LocalStrategy = require('passport-local').Strategy
4
5 // Adicionar após: mongoose.connect(app.get('mongo_url'))
6 app.use(passport.initialize())
7 app.use(passport.session())
8 passport.use(new LocalStrategy(require('./../../schemas/users').authenticate()))
9 passport.serializeUser(require('./../../schemas/users').serializeUser())
10 passport.deserializeUser(require('./../../schemas/users').deserializeUser())

Estamos inicializando as bibliotecas passport e express-session, para conseguirmos desenvolver o


sistema de login posteriormente.
Deixamos o passport configurado para podermos utilizar durante o cadastro de um novo usuário e,
também, na atualização dos usuários já existentes.
Após estas configurações, devemos acessar o arquivo src/schemas/users.js. Veja a alteração neste
arquivo:
Criando Login e Logout 70

1 const mongoose = require('mongoose')


2 const passportLocalMongoose = require('passport-local-mongoose')
3
4 const User = new mongoose.Schema({
5 name: {
6 type: String,
7 required: true
8 },
9 slug: {
10 type: String,
11 required: true
12 },
13 email: {
14 type: String,
15 required: true
16 },
17 password: {
18 type: String,
19 required: true
20 }
21 })
22
23 User.plugin(passportLocalMongoose, { usernameField: 'email' })
24
25 module.exports = mongoose.model('User', User);

Carregamos o plugin passport-local-mongoose, que é uma extensão do mongoose. Em seguida,


informamos que o campo de usernameField do plugin será substituído pelo campo de email.
Fazemos isso, porque não possuímos um campo de username.
Após a implementação modificaremos um pouco o CRUD de users. Veja as modificações:

1 // services/users/create.js
2 const Users = require('./../../schemas/users')
3
4 module.exports = (req, res) => {
5 req.body.slug = req.body.name.toLowerCase().replace(/ /g, '-')
6
7 Users
8 .register(req.body, req.body.password, (err, account) => {
9 if (err) {
10 return res.send('Error ' + error)
11 }
Criando Login e Logout 71

12
13 return res.redirect('/users')
14 })
15 }
16
17 // services/users/update.js
18 const Users = require('./../../schemas/users')
19
20 module.exports = (req, res) => {
21 Users
22 .findById(req.params.id)
23 .then((user) => {
24 user.password = req.body.password
25
26 user.setPassword(user.password, (err, updated, passErr) => {
27 if (err || passErr) {
28 return res.send('Error: ' + error)
29 }
30
31 user.save()
32
33 user.email = req.body.email
34 user.name = req.body.name
35
36 user.save()
37 return res.redirect('/users')
38
39 })
40 })
41 .catch((error) => {
42 return res.send('Error: ' + error)
43 })
44 }

Observe que os métodos continuam parecidos, porém quem se encarrega de fazer a persistência no
banco de dados são as bibliotecas que instalamos em conjunto com a programação.
Note que verificamos o tipo de usuário que está tentando fazer a persistência usando o código
(req.body.password). Caso não exista este campo, significa que o usuário não está logado e,
consequentemente, não tem acesso e nem permissão para executar a ação de persistência em banco.
Utilizamos o método register para criação e o método save para atualizações. Perceba que estamos
utilizando duas vezes o método save, em nosso serviço de update. Não é um erro. Precisamos executar
duas vezes porque o método setPassword não suportaria salvar outros dados a não ser a password.
Criando Login e Logout 72

Logo, primeiro salvamos o password e em seguida salvamos os demais campos do schema.


Veja a diferença entre criar um usuário utilizando as bibliotecas instaladas e sem utilizá-las:

Robomongo Passport

A biblioteca passport, juntamente com passport-local-mongoose, tratam da segurança e criam


campos de validações extras. Veja que não precisamos fazer nada para isso.

Criando login
Para a criação de login, manteremos a mesma regra utilizada até agora. Criaremos:

• o endpoint principal
• o arquivo de rotas dentro deste endpoint
• as views
• os serviços.

Primeiro, crie uma pasta chamada login dentro da pasta src/views e dentro desta pasta um arquivo
chamado index.hbs.
Criando Login e Logout 73

1 <div class="panel panel-default">


2 <div class="panel-heading">Login</div>
3 <div class="panel-body">
4 <form action="/login" method="POST">
5 <div class="form-group">
6 <label for="email">Email</label>
7 <input type="email" name="email" placeholder="Enter your email" clas\
8 s="form-control">
9 </div>
10 <div class="form-group">
11 <label for="password">Password</label>
12 <input type="password" name="password" placeholder="Enter your passw\
13 ord" class="form-control">
14 </div>
15 <div class="form-group">
16 <button type="submit" class="btn btn-success">Login</button>
17 </div>
18 </form>
19 </div>
20 </div>

Dentro da pasta src/routes, crie uma pasta chamada login e dentro desta pasta, um arquivo index.js
com o seguinte conteúdo:

1 const express = require('express')


2 const router = express.Router();
3
4 router.get('/', require('./../../services/login'))
5 router.post('/', require('./../../services/login/login'))
6
7 module.exports = router

Depois, definiremos o endpoint principal em nosso arquivo src/index.js. Veja o código:

1 module.exports = (app) => {


2 app.use('/', require('./routes/main'))
3 app.use('/users', require('./routes/users'))
4 app.use('/rooms', require('./routes/rooms'))
5 app.use('/login', require('./routes/login'))
6 }

Agora, crie a pasta login em src/services e dentro desta pasta, o arquivo index.js e login.js, com
os seguintes códigos, respectivamente:
Criando Login e Logout 74

1 // arquivo index.js
2 module.exports = (req, res) => {
3 return res.render('login/index', {
4 title: 'Login - ChatSchool Admin'
5 })
6 }

1 // arquivo login.js
2 const Users = require('./../../schemas/users')
3
4 module.exports = (req, res) => {
5 Users
6 .authenticate()(req.body.email, req.body.password, (err, user, options) => {
7 if (err) {
8 return res.send('Error: ' + err)
9 }
10
11 return req.login(user, (err) => {
12 if (err) {
13 return
14 }
15
16 return res.redirect('/')
17 })
18 })
19 }

Desta forma, caso acesse http://localhost:9000/login, você deverá ser redirecionado para a view de
login e, caso adicione os dados de autenticação corretos, você deverá ser redirecionado para a página
raiz do sistema de administração. Se houver algum erro de renderização, pode tentar rodar o gulp
novamente, no terminal.
Depois de fazer o login e estar autenticado, note que qualquer usuário ainda tem acesso a qualquer
ponto do sistema, mas isso não é correto. Apenas os usuários autenticados deveriam ter este acesso.
Para resolver este problema, criaremos um recurso chamado middleware. Crie uma pasta com este
nome dentro da pasta src/routes e dentro desta pasta um arquivo chamado isloggedin.js e adicione
o seguinte código:
Criando Login e Logout 75

1 module.exports = (req, res, next) => {


2 if (req.user) {
3 return next()
4 }
5
6 return res.redirect('/login')
7 }

Observe que este código verifica se os dados do usuário existem na requisição. Se existir, ele continua
a execução. Caso contrário, ele redireciona para a página de login obrigando o usuário estar logado
para ter acesso.
Apenas criar este arquivo e adicionar este código não faz com que o middleware funcione.
Precisamos aplicá-lo em nossas rotas.
Aplicaremos na rota src/routes/main/index.js, para que você tenha o primeiro exemplo de aplica-
ção:

1 const express = require('express')


2 const router = express.Router()
3
4 const isLoggedIn = require('./../middleware/isloggedin')
5
6 router.get('/', isLoggedIn, require('./../../services/main'))
7
8 module.exports = router

Note que estamos aplicando o middleware, assim como aplicamos as nossas validações.
Depois aplicar a rota main, reinicie o gulp e tente acessar novamente a rota raiz http://localhost:9000/.
Você será redirecionado para a página de login. Significa que o sistema de autenticação está
funcionando. Basta aplicar as demais rotas:

1 // arquivo src/routes/users/index.js
2 const express = require('express')
3 const router = express.Router()
4
5 const isLoggedIn = require('./../middleware/isloggedin')
6
7 const createRules = require('./../validator/users/create')
8 const editRules = require('./../validator/users/edit')
9 const removeRules = require('./../validator/users/remove')
10 const updateRules = require('./../validator/users/update')
11
Criando Login e Logout 76

12 router.get('/', isLoggedIn, require('./../../services/users/index'))


13 router.get('/new', isLoggedIn, require('./../../services/users/new'))
14 router.get('/edit/:id', isLoggedIn, editRules, require('./../../services/users/edit'\
15 ))
16 router.get('/:id', isLoggedIn, require('./../../services/users/show'))
17 router.post('/', isLoggedIn, createRules, require('./../../services/users/create'))
18 router.put('/:id', isLoggedIn, updateRules, require('./../../services/users/update'))
19 router.patch('/:id', isLoggedIn, updateRules, require('./../../services/users/update\
20 '))
21 router.delete('/:id', isLoggedIn, removeRules, require('./../../services/users/remov\
22 e'))
23
24 module.exports = router
25
26 // arquivo src/routes/rooms/index.js
27 const express = require('express')
28 const router = express.Router()
29
30 const isLoggedIn = require('./../middleware/isloggedin')
31
32 const createRules = require('./../validator/rooms/create')
33 const editRules = require('./../validator/rooms/edit')
34 const removeRules = require('./../validator/rooms/remove')
35 const updateRules = require('./../validator/rooms/update')
36
37 router.get('/', isLoggedIn, require('./../../services/rooms/index'))
38 router.get('/new', isLoggedIn, require('./../../services/rooms/new'))
39 router.get('/edit/:slug', isLoggedIn, editRules, require('./../../services/rooms/edi\
40 t'))
41 router.get('/:id', isLoggedIn, require('./../../services/rooms/show'))
42 router.post('/', isLoggedIn, createRules, require('./../../services/rooms/create'))
43 router.put('/:id', isLoggedIn, updateRules, require('./../../services/rooms/update'))
44 router.patch('/:id', isLoggedIn, updateRules, require('./../../services/rooms/update\
45 '))
46 router.delete('/:id', isLoggedIn, removeRules, require('./../../services/rooms/remov\
47 e'))
48
49 module.exports = router

Basta rodar o gulp, novamente, para evitar erros e pronto!. Já estamos com todas as rotas protegidas,
através deste middleware.
Para termos uma segurança a mais, podemos adicionar um handlebar no arquivo src/views/parti-
als/main/header.hbs. Desta forma, ocultaremos o menu principal caso o usuário não esteja logado.
Criando Login e Logout 77

Antes disso, o usuário não tinha acesso mas ele ainda via as opções possíveis no menu. Com esta
adaptação, ele não consegue exergar o menu principal.

1 <header id="header">
2 <nav class="navbar navbar-inverse">
3 <div class="container-fluid">
4 <div class="nav-header">
5 <button type="button" class="navbar-toggle collapsed" data-toggle="c\
6 ollapse" data-target="#nav">
7 <span class="sr-only"></span>
8 <span class="icon-bar"></span>
9 <span class="icon-bar"></span>
10 <span class="icon-bar"></span>
11 </button>
12 <a href="/" class="navbar-brand">ChatSchool</a>
13 </div>
14 {{#if user_logged}}
15 <div class="collapse navbar-collapse" id="nav">
16 <ul class="nav navbar-nav">
17 <li class="active">
18 <a href="/">Home</a>
19 </li>
20 <li class="dropdown">
21 <a data-toggle="dropdown" class="dropdown-toggle" href="\
22 ">
23 Users
24 <span class="caret"></span>
25 </a>
26 <ul class="dropdown-menu">
27 <li>
28 <a href="/users">List</a>
29 <a href="/users/new">Create</a>
30 </li>
31 </ul>
32 </li>
33 <li class="dropdown">
34 <a data-toggle="dropdown" class="dropdown-toggle" href="\
35 ">
36 Rooms
37 <span class="caret"></span>
38 </a>
39 <ul class="dropdown-menu">
40 <li>
Criando Login e Logout 78

41 <a href="/rooms">List</a>
42 <a href="/rooms/new">Create</a>
43 </li>
44 </ul>
45 </li>
46 </ul>
47 </div>
48 {{/if}}
49 </div>
50 </nav>
51 </header>

Repare no handlebar {{#if user_logged}} {{/if}}.


Verificamos se existe uma variável com o nome de user_logged. Se existir, ele exibe o menu. Caso
não exista, o menu estará oculto. Ainda não criamos esta variável e não atribuímos nenhum valor a
ela.
Teremos que passar esta variável como parâmetro para todas as views que renderizam o menu, para
que ele seja exibido. Passamos estes valores para as views através dos serviços.

1 // src/services/main/index.js
2 module.exports = (req, res) => {
3 return res.render('main/index', {
4 title: 'Chatschool - Admin',
5 user_logged: req.user
6 })
7 }
8
9 // src/services/users/index.js
10 return res.render('users/index', {
11 title: 'Users - ChatSchool Admin',
12 users,
13 user_logged: req.user
14 })
15
16 // src/services/users/edit.js
17 return res.render('users/edit', {
18 title: 'Users - ChatSchool Admin',
19 user,
20 user_logged: req.user
21 })
22
23 // src/services/users/new.js
Criando Login e Logout 79

24 return res.render('users/create', {
25 title: 'Users - Chatschool Admin',
26 user,
27 user_logged: req.user
28 })
29
30 // src/services/rooms/index.js
31 return res.render('rooms/index', {
32 title: 'Rooms - ChatSchool Admin',
33 rooms,
34 user_logged: req.user
35 })
36
37 // src/services/rooms/edit.js
38 return res.render('rooms/edit', {
39 title: 'Rooms - ChatSchool Admin',
40 room,
41 user_logged: req.user
42 })
43
44 // src/services/rooms/new.js
45 return res.render('rooms/create', {
46 title: 'Rooms - ChatSchool Admin',
47 room,
48 user_logged: req.user
49 })

Adicionando a variável em todas as rotas, temos o sistema todo protegido de usuários não
autenticados.
FrontEnd
Iniciando Projeto FrontEnd
Iniciaremos o desenvolvimento do frontend do projeto.
O frontend é toda parte visual e, também, HTML e CSS.
Melhoraremos o layout do chat, conforme for necessário, até chegarmos ao objetivo final de layout.
Para isso, seguiremos os seguintes passos:

• Faremos toda a estruturação e marcação com o HTML


• Adicionaremos os estilos com o CSS
• Adicionaremos algumas funcionalidades com o javascript
• Definiremos qual biblioteca javascript utilizaremos

O foco inicial será no webapp. Nos próximos capítulos criaremos toda parte de estilização da área
administrativa.
Até este ponto do projeto tínhamos a pasta principal chamada chatschool e dentro desta pasta os
arquivos do projeto. A partir de agora mudaremos a estrutura.
Dentro da pasta chatschool teremos outras duas pastas:
admin e webapp.
Crie estas duas pastas. Mova todos os arquivos que tenha na pasta raiz para a pasta admin.
Começaremos a desenvolver a estrutura de layout na pasta webapp.
Dentro da pasta webapp, crie uma outra pasta chamada src. Após a criação desta pasta, acesse o
terminal e começará a trabalhar com o gerenciador de pacotes do node. O famoso npm.
Dentro da pasta webapp rode o seguinte comando:

npm init

O comando exigirá algumas informações e você deverá preencher. O importante será o arquivo final
que este comando gera:
package.json
Este arquivo será responsável por gerenciar as dependências do projeto. Veja o exemplo incial do
arquivo:
FrontEnd 81

1 {
2 "name": "chatschool",
3 "version": "1.0.0",
4 "description": "chatschool, nodejs, socketio",
5 "main": "index.js",
6 "scripts": {
7 "test": "echo \"Error: no test specified\" && exit 1"
8 },
9 "keywords": [
10 "chatschool",
11 "nodejs",
12 "socketio"
13 ],
14 "author": "",
15 "license": "ISC"
16 }

Utilizaremos algumas bibliotecas que foram usadas para o desenvolvimento do admin. Podemos
pegar a base do arquivo package.json.
Deixaremos a parte de instalação de dependências para outro momento, mas já deixe o arquivo
package.json criado na pasta raiz. Focaremos, agora, na pasta src e sua arquitetura.

• src
– fonts
– js
– scss

A partir desta estrutura começaremos a desenvolver.

Configurando gulp
Começaremos configurando o gulp, para que nossas tarefas sejam todas automatizadas.
O primeiro passo:

• determinar as dependências do projeto


• configurar o arquivo gulpfile.js.

Ainda não criamos o arquivo gulpfile. Crie o arquivo na raiz da pasta chatschool/webapp.
Para o frontend, utilizaremos as seguintes bibliotecas:
FrontEnd 82

• gulp
• gulp-notify
• gulp-livereload
• gulp-changed
• gulp-util
• del
• gulp-concat
• gulp-plumber
• gulp-imagemin
• gulp-minify-css
• gulp-minify-html
• gulp-rev
• gulp-rev-collector
• gulp-uglify
• gulp-sass
• gulp-connect

Podemos instalar todas as dependências com apenas um comando. Veja o comando abaixo:

npm install gulp gulp-notify gulp-livereload gulp-changed gulp-util del gulp-concat \


gulp-plumber gulp-imagemin gulp-minify-css gulp-minify-html gulp-rev gulp-rev-collec\
tor gulp-uglify gulp-sass gulp-connect node-neat --save-dev

Após a conclusão das instalações, começaremos a configurar o arquivo gulpfile.js. Este arquivo será
muito parecido com o arquivo gulpfile da seção admin, que já criamos.
Lembrando que trabalhamos com o gulp para tentarmos melhorar a produtividade durante o
desenvolvimento.
A grande diferença entre a parte administrativa e o frontend, é que no admin temos um server e no
frontend, não.
Para resolvermos este problema, instalaremos uma biblioteca que tem uma importância gigantesca
dentro do projeto. Esta biblioteca se chama gulp-connect e é responsável por criar um servidor,
automaticamente, assim que rodarmos o comando gulp.
Veja o código do arquivo gulpfile.js.
FrontEnd 83

1 const notify = require('gulp-notify')


2 const gutil = require('gulp-util')
3 const livereload = require('gulp-livereload')
4 const changed = require('gulp-changed')
5 const concat = require('gulp-concat')
6 const plumber = require('gulp-plumber')
7 const imagemin = require('gulp-imagemin')
8 const minifyCss = require('gulp-minify-css')
9 const minifyHtml = require('gulp-minify-html')
10 const rev = require('gulp-rev')
11 const revCollector = require('gulp-rev-collector')
12 const del = require('del')
13 const uglify = require('gulp-uglify')
14 const connect = require('gulp-connect')
15 const sass = require('gulp-sass')
16 const gulp = require('gulp')
17
18 const paths = {
19 fontsSrc: 'src/fonts/',
20 htmlSrc: 'src/',
21 sassSrc: 'src/scss/',
22 jsSrc: 'src/js/',
23 imgSrc: 'src/images/',
24
25 buildDir: 'build/',
26 distDir: 'dist/',
27 revDir: 'build/rev/'
28 }
29
30 let onError = (err) => {
31 gutil.beep()
32 gutil.log(gutil.colors.red(err))
33 }
34
35 gulp.task('build-html', () => {
36 return gulp
37 .src(paths.htmlSrc.concat('**/*.html'))
38 .pipe(gulp.dest(paths.buildDir.concat('/')))
39 .pipe(livereload())
40 })
41
42 gulp.task('build-css', () => {
43 return gulp
FrontEnd 84

44 .src(paths.sassSrc.concat('**/*.scss'))
45 .pipe(sass({
46 includePaths: require('node-neat').includePaths,
47 style: 'nested',
48 onError: function(){
49 console.log('SASS ERROR!')
50 }
51 }))
52 .pipe(plumber({ errorHandler: onError }))
53 .pipe(gulp.dest(paths.buildDir.concat('/css')))
54 .pipe(livereload())
55 })
56
57 gulp.task('build-js', () => {
58 return gulp
59 .src(paths.jsSrc.concat('*.js'))
60 .pipe(plumber({ errorHandler: onError }))
61 .pipe(changed(paths.buildDir.concat('/js')))
62 .pipe(gulp.dest(paths.buildDir.concat('/js')))
63 .pipe(livereload())
64 })
65
66 gulp.task('build-fonts', () => {
67 return gulp
68 .src(paths.fontsSrc.concat('**/*.*'))
69 .pipe(gulp.dest(paths.buildDir.concat('/fonts')))
70 .pipe(livereload())
71 })
72
73 gulp.task('build-images', () => {
74 return gulp
75 .src(paths.imgSrc.concat('**/*.+(png|jpeg|gif|jpg|svg)'))
76 .pipe(changed(paths.buildDir.concat('/images')))
77 .pipe(gulp.dest(paths.buildDir.concat('/images')))
78 .pipe(livereload())
79 })
80
81 gulp.task('build', ['build-html', 'build-css', 'build-js', 'build-images', 'build-fo\
82 nts'], () => {
83 return connect.server({
84 root: 'src',
85 livereload: true
86 })
FrontEnd 85

87 })
88
89 gulp.task('watch', () => {
90 gulp.watch(['src/*.html'], ['build-html']);
91 gulp.watch('src/scss/**', ['build-css']);
92 gulp.watch(paths.jsSrc + '**/*.js', ['build-js']);
93 gulp.watch(paths.imgSrc + '**/*.+(png|jpeg|jpg|svg)', ['build-images']);
94 })
95
96 const env = process.env.NODE_ENV || 'development'
97
98 if (env === 'development') {
99 return gulp.task('default', ['build', 'watch'])
100 }

Compare com o arquivo gulpfile.js, criado para o setor administrativo e verá que há poucas
diferenças.
Após criar este arquivo com toda configuração necessária, crie um arquivo chamado index.html,
dentro da pasta webapp/src. Dentro deste arquivo incluiremos algumas dependências.
Veja o código abaixo:

1 <!doctype html>
2 <html lang="en">
3 <head>
4 <meta charset="UTF-8">
5 <meta name="viewport"
6 content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-\
7 scale=1.0, minimum-scale=1.0">
8 <meta http-equiv="X-UA-Compatible" content="ie=edge">
9
10 <title>Document</title>
11 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-boot\
12 strap/3.3.7/css/bootstrap.min.css">
13 </head>
14 <body>
15
16 </body>
17 <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></sc\
18 ript>
19 <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/boots\
20 trap.min.js"></script>
FrontEnd 86

21 </body>
22 </html>

Temos o gulp configurado, com todas as tarefas automatizadas e o layout carregando o Bootstrap.
Veja que estamos chamando a biblioteca jQuery, que é uma exigência do Bootstrap, uma vez que
ele a utiliza para várias funcionalidades.

Marcando aside - chatbar


Agora faremos as marcações HTML do chat. Desta forma, estaremos desenvolvento o frontend.
Dividiremos o layout em duas partes principais: chatbar e chatbox.

Elementos do layout

Posição Descrição
chatbar Listagem de salas e usuários
chatbox Corpo das mensagens

Esta estrutura é bem comum em diversas aplicações de chats.


Dentro destes elementos principais terão sub-estruturas com diversos outros elementos e marcações.
Dividindo em uma visão macro, podemos separar nestes dois blocos principais, para facilitar o
entendimento inicial da estrutura de front.
Como estamos usando o Bootstrap, utilizaremos a estrutura de grids, para separar os blocos.

1 <body>
2 <main>
3 <aside class="col-xs-6 col-md-2">
4 SIDEBAR
5 </aside>
6 </main>
7 </body>

Se olharmos no navegador, ainda não temos modificação com o código acima. Como ainda não
testamos a configuração do gulpfile.js, este é o momento para testar.
No terminal, onde se encontra o arquivo gulpfile.js, rode o comando gulp.

gulp
ou
gulp default
FrontEnd 87

Este comando foi configurado para executar a tarefa build e watch, que serão responsáveis por
executar todas as tarefas e ficar assistindo para ver todas as alterações no projeto e refazer todas as
tarefas necessárias.
Trabalharemos com a biblioteca de fontes Awesome. Faremos a inclusão no arquivo HTML para
que possamos utilizar os recursos a qualquer momento.

<!-- BOOTSTRAP -->


<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstra\
p/3.3.7/css/bootstrap.min.css">
<!-- FONT AWESOME -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7\
.0/css/font-awesome.css">

Incluímos, logo abaixo do carregamento do Bootstrap, que já existia.


Em seguida, podemos prosseguir com a sidebar. Criaremos a estrutura que utilizaremos no projeto
final, apenas com marcações HTML, para que possa começar a ter uma ideia de como ficará o projeto.

1 <!doctype html>
2 <html lang="en">
3 <head>
4 <meta charset="UTF-8">
5 <meta name="viewport"
6 content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-\
7 scale=1.0, minimum-scale=1.0">
8 <meta http-equiv="X-UA-Compatible" content="ie=edge">
9
10 <title>Document</title>
11 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-boot\
12 strap/3.3.7/css/bootstrap.min.css">
13 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome\
14 /4.7.0/css/font-awesome.css">
15 </head>
16 <body>
17 <main>
18 <aside class="col-xs-6 col-md-2">
19 <i class="fa fa-commenting-o"></i>
20 <h4>ChatSchool</h4>
21 <ul>
FrontEnd 88

22 <li>
23 <h4>Canais</h4>
24 </li>
25 <li>Canal 1</li>
26 </ul>
27 <ul>
28 <li>
29 <h4>Mensagens</h4>
30 </li>
31 <li>Fabrício</li>
32 </ul>
33 </aside>
34 </main>
35
36 <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js">\
37 </script>
38 <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/b\
39 ootstrap.min.js"></script>
40 </body>
41 </html>

Depois, estilizaremos para que fique mais bonito e com a cara do projeto final. Por enquanto, não se
preocupe com a estética, apenas com a estrutura e a marcação HTML.
Recarregue o navegador e conseguirá visualizar as modificações.

Marcando section - chatbox


Agora faremos a marcação HTML da seção chatbox.
Caso tenha alguma dúvida sobre a estrutura de grid do Bootstrap, temos um conteúdo sobre este
framework CSS na School of Net, que poderá ajudá-lo.

1 <!doctype html>
2 <html lang="en">
3 <head>
4 <meta charset="UTF-8">
5 <meta name="viewport"
6 content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-\
7 scale=1.0, minimum-scale=1.0">
8 <meta http-equiv="X-UA-Compatible" content="ie=edge">
9
10 <title>Document</title>
FrontEnd 89

11 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-boot\


12 strap/3.3.7/css/bootstrap.min.css">
13 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome\
14 /4.7.0/css/font-awesome.css">
15 </head>
16 <body>
17 <main>
18 <aside class="col-xs-6 col-md-2">
19 <i class="fa fa-commenting-o"></i>
20 <h4>ChatSchool</h4>
21 <ul>
22 <li>
23 <h4>Canais</h4>
24 </li>
25 <li>Canal 1</li>
26 </ul>
27 <ul>
28 <li>
29 <h4>Mensagens</h4>
30 </li>
31 <li>Fabrício</li>
32 </ul>
33 </aside>
34 <section class="col-xs-6 col-md-10">
35 <h1>Chatbox</h1>
36 <header class="col-xs-12">
37 <h2>Header chatbox</h2>
38 </header>
39 <div class="col-xs-12">
40 <h2>Conversation frame</h2>
41 </div>
42 <div class="col-xs-12">
43 <form>
44 <input type="text" name="message" class="form-control" placehold\
45 er="Type your message here">
46 </form>
47 </div>
48 </section>
49 </main>
50 <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js">\
51 </script>
52 <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/b\
53 ootstrap.min.js"></script>
FrontEnd 90

54 </body>
55 </html>

Esta é a estrutura final da marcação.


Observe que temos as duas seções principais do chat marcadas de forma bem primitiva.
A partir de agora começaremos a estilizar com CSS e deixar da forma que desejarmos com conteúdos
testes. Depois, aplicaremos a programação em cima da estrutura.
A estrutura do chatbox é composta:

• um header onde estarão os dados da mensagem e uma busca por conteúdo


• o campo principal das mensagens
• um rodapé com um campo input, onde digitaremos as mensagens para serem enviadas

Lembrando que estamos, apenas, fazendo marcações sem nenhuma estilização.

Estilizando aside - chatbar


Para começarmos a estilizar o bloco chatbar, precisaremos fazer algumas configurações.
Em primeiro lugar, crie um arquivo chamado application.scss, dentro da pasta webapp/src/scss.
Dentro desta mesma pasta, crie outros dois arquivos: _default.scss e _chatbar.scss. Para cada bloco
ou elemento específico, criaremos uma novo arquivo independente para trabalharmos de forma
organizada.
Não precisa se preocupar, porque todos serão compilados e adicionados em um único arquivo, que
carregaremos no arquivo index.html.
O mais importante é lembrar de importar todos os arquivos criados para dentro do arquivo
application.scss, para que os mesmos tenham efeito.
Veja o código abaixo:

1 @import "./_default.scss";
2 @import "./_chatbar.scss";

Estamos quase prontos para conseguirmos estilizar a sidebar. Para isso, precisaremos executar dois
procedimentos simples:

1. Chamar o arquivo principal de CSS no arquivo index.html.


2. Alterar a pasta base no arquivo gulpfile.js.

Primeiro, vamos alterar o arquivo gulpfile.js.


Veja como ficou o trecho do código alterado:
FrontEnd 91

1 gulp.task('build', ['build-html', 'build-css', 'build-js', 'build-images', 'build-fo\


2 nts'], () => {
3 return connect.server({
4 root: 'build',
5 livereload: true
6 })
7 })

Alteramos a pasta root de configuração, de src para build.


Depois, basta acrescentarmos o arquivo principal de estilização (application.css), que será gerado
pelo gulp no projeto.

1 <head>
2 <meta charset="UTF-8">
3 <meta name="viewport"
4 content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-\
5 scale=1.0, minimum-scale=1.0">
6 <meta http-equiv="X-UA-Compatible" content="ie=edge">
7
8 <title>Document</title>
9 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-boot\
10 strap/3.3.7/css/bootstrap.min.css">
11 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome\
12 /4.7.0/css/font-awesome.css">
13 <link rel="stylesheet" href="./css/application.css">
14 </head>

Veja que, o último arquivo de estilização que estamos incluindo é o application.css. Este arquivo é
criado pelas tarefas do gulp, dentro da pasta webapp/build/css/.
Definimos a pasta root como sendo build. Basta acessar a pasta css com o nome do arquivo principal.
Rode o comando gulp, novamente, e você terá a pasta build criada e o css sendo carregado,
corretamente.
A partir de agora alteraremos os arquivos da pasta webapp/src. O browser renderizará os arquivos
da pasta webapp/build, pois é para lá que as tarefas estão enviando os arquivos compilados.

Código do arquivo index.html


FrontEnd 92

1 <aside class="col-xs-6 col-md-2 chatbar">


2 <div class="col-xs-12 title">
3 <i class="fa fa-commenting-o"></i>
4 <h4>ChatSchool</h4>
5 </div>
6 <ul class="list-group channels">
7 <li class="list-group-item title">
8 <h4>Canais(3)</h4>
9 </li>
10 <li class="list-group-item"><i class="fa fa-comment-o"></i> Canal 1</li>
11 <li class="list-group-item"><i class="fa fa-comment-o"></i> Canal 2</li>
12 <li class="list-group-item"><i class="fa fa-comment-o"></i> Canal 3</li>
13 </ul>
14 <ul class="list-group messages">
15 <li class="list-group-item title">
16 <h4>Mensagens</h4>
17 </li>
18 <li class="list-group-item">fabricio</li>
19 </ul>
20 </aside>

Código do arquivo _default.scss


1 html {
2 min-height: 100%;
3 height: 100%;
4 }
5
6 body {
7 min-height: 100%;
8 height: 100%;
9 background: #fefefe;
10 }
11
12 main {
13 min-height: 100%;
14 height: 100%;
15 }

Código do arquivo _chatbar.scss


FrontEnd 93

1 .chatbar {
2 padding: 0;
3 background: #3C382C;
4 min-height: 100%;
5 height: 100%;
6
7 .title {
8 color: white;
9 background: #322F24;
10 padding: 1rem 15px;
11 h4 {
12 display: inline-block;
13 }
14 }
15
16 .channels {
17 margin: 6rem 0;
18 .list-group-item {
19 border: 0;
20 background: #3C382C;
21 color: #857D64;
22 cursor: pointer;
23 &.title {
24 h4 {
25 display: inline-block;
26 text-transform: uppercase;
27 font-weight: bold;
28 font-size: 1em;
29 }
30 }
31 &:hover, &:active {
32 background: #322F24;
33 }
34 }
35 }
36
37 .messages {
38 margin: 3.5rem 0;
39 .list-group-item {
40 border: 0;
41 background: #3C382C;
42 color: #857D64;
43 text-transform: capitalize;
FrontEnd 94

44 cursor: pointer;
45 &.title {
46 h4 {
47 display: inline-block;
48 text-transform: uppercase;
49 font-weight: bold;
50 font-size: 1em;
51 }
52 }
53 &:hover, &:active {
54 background: #322F24;
55 }
56 }
57 }
58 }

Observe o que fizemos:

• no arquivo _default.scss foi adicionado tudo que será comum entre os blocos principais.
• adicionamos a classe chatbar na tag aside, para que todo CSS presente no arquivo _chat-
bar.scss fosse aplicado a sidebar.
• estilizamos o título principal com um background diferente do background da sidebar, para dar
mais destaque.
• definimos a sidebar com 100% da altura, para que pegasse toda a extensão da página.
• estilizamos os títulos, de cada item da lista presente na marcação.

Depois de adicionar estas estilizações, você poderá ver a diferença no layout.


Note que temos um destaque quando passamos o mouse sobre os elementos da sidebar.
Chegamos ao padrão que desejávamos para esta etapa do projeto.
Ainda existem alterações a serem feitas, mas adicionaremos, conforme a necessidade.
Qualquer dúvida de Boostratp ou até mesmo CSS puro, você pode pesquisar na School of Net.

Estilizando section - chatbox


Estilizaremos o segundo bloco principal, que é o chatbox.
Para que o CSS tivesse efeito, adicionamos a classe chatbox na tag section do HTML. Crie o arquivo
_chatbox.scss junto com os demais e carregue-o no arquivo principal application.scss.

Código do arquivo index.html


FrontEnd 95

1 <section class="col-xs-6 col-md-10 chatbox">


2 <header class="col-xs-12 header">
3 <span class="username">@leonan</span>
4 </header>
5 <div class="col-xs-12 conversation">
6 <div class="col-xs-12 message">
7 <div class="avatar col-xs-6 col-md-1">
8 <h2>L</h2>
9 </div>
10 <p class="text col-xs-6 col-md-11">This is my first message</p>
11 </div>
12 <div class="col-xs-12 message">
13 <div class="avatar col-xs-6 col-md-1">
14 <h2>L</h2>
15 </div>
16 <p class="text col-xs-6 col-md-11">This is my first message</p>
17 </div>
18 <div class="col-xs-12 message">
19 <div class="avatar col-xs-6 col-md-1">
20 <h2>L</h2>
21 </div>
22 <p class="text col-xs-6 col-md-11">This is my first message</p>
23 </div>
24 </div>
25 <div class="col-xs-12 type">
26 <form>
27 <input type="text" class="form-control" name="message" placeholder="Type\
28 your message here">
29 </form>
30 </div>
31 </section>

Código do arquivo _chatbox.scss


FrontEnd 96

1 .chatbox {
2 padding: 0;
3 .header {
4 padding: 1rem 15px;
5 border-bottom: 1px solid #ebebeb;
6 .username {
7 font-size: 1em;
8 font-weight: bold;
9 }
10 }
11 .conversation {
12 .message {
13 &.first-child {
14 margin: 1em 0;
15 }
16 margin: 1em 0 0 0;
17 .avatar {
18 border-radius: 10%;
19 border: 1px solid #ebebeb;
20 padding: 1rem 0;
21 background: #3C382C;
22 h2 {
23 color: white;
24 text-align: center;
25 margin: 0;
26 font-weight: bold;
27 }
28 }
29 .text {
30 padding: 0.8em 15px;
31 font-size: 1.2em;
32 margin: 0;
33 font-weight: 300;
34 }
35 }
36 }
37 .type {
38 margin: 1rem 0 0 0;
39 form {
40 .form-control {
41 border: 1px solid #3C382C;
42 &:focus {
43 box-shadow: none;
FrontEnd 97

44 }
45 }
46 }
47 }
48 }

Observe o que foi feito:

• no bloco de chatbox, decidimos destacar e separar o header. Criamos uma classe header e
estilizamos, adicionando uma borda inferior
• no bloco de mensagens, adicionamos uma classe principal chamada conversation
• no último bloco, definimos uma classe type, para estilizar o formulário de envio de mensagem
• na seção de mensagens, definimos o layout para que o avatar do usuário seja deslocado para
esquerda e a mensagem para a direita
• adicionamos três mensagens, manualmente, para que você possa ter uma ideia de como ficará
o layout final
• configuramos um espaçamento padrão para as mensagens, para que fique bem alinhado e
legível

Em seguida, faremos com que estas informações venham do banco de dados e faremos todas as
estilizações que forem necessárias.
BackEnd
Configurando Express
O frontend está pronto. Começaremos a trabalhar com a API, para consumir os dados vindos da
parte administrativa do projeto. Os dados serão consumidos e mostrados em um formato JSON.
Utilizaremos um projeto open source chamado Socket.io, juntamente com websocket, para nos
auxiliar nesta etapa.
Caso queira saber mais sobre o projeto Socket.io, acesse https://socket.io/. Neste site você terá acesso
a tudo que precisa saber, inclusive poderá contar com a documentação.
Este projeto facilita o trabalho em aplicações que utilizam o protocolo websocket, abstraindo alguns
métodos, tornando o trabalho muito mais produtivo.
A instalação é muito simples e o projeto é sólido, o que deve deixá-lo tranquilo para utilizar em suas
aplicações.

Criando a API com express


Siga os passos abaixo:

• Rode o comando abaixo, no terminal, para instalar o Express Generator

npm install express-generator -g

• Após instalado, rode o comando abaixo, para criar o esqueleto da api. Não se esqueça que deve
estar na pasta chatschool, pois será outro projeto além de admin e webapp.

express api

Você terá a mensagem de confirmação da criação do projeto.


Após a criação do projeto, acesse a pasta api e verifique a seguinte estrutura criada:
BackEnd 99

Estrutura criada pelo express-generator

Diretório/Arquivo Descrição
app.js Arquivo inicial da aplicação
bin/ Pasta de executáveis da aplicação
package.json Arquivo gerenciador de dependências
public/ Pasta disponibilizada ao usuário para acesso
routes/ Pasta com todas as rotas ou endpoints, da aplicação
views/ Pasta com todas as views da aplicação

Instale todas as dependências inicias, rodando o comando abaixo, dentro da pasta api:

npm install

Além das dependências iniciais, precisaremos instalar mais algumas bibliotecas. Rode o comando
abaixo:

npm install mongoose --save

Depois de instalar a biblioteca mongoose, temos que fazer uma conexão para efetuarmos as buscas
no ambiente de desenvolvimento. Adicione o código abaixo no arquivo app.js da pasta raiz.

1 // Carregando mongoose
2 var mongoose = require('mongoose');
3
4 // Criando conexão
5 mongoose.connect('mongodb://127.0.0.1:27017/chatschool_dev');

Após adicionar os códigos acima, testaremos a conexão. Lembrando que você pode ter que iniciar o
serviço do mongodb na máquina, para deixar aguardando conexões, caso ainda não esteja rodando.
Para ativar, basta acessar a pasta de instalação do mongodb e rodar o comando abaixo:
Windows
Acesse a pasta binária da instalação do MongoDB (c:Program FilesMongoDBServer\3.2\bin) e rode
o comando abaixo:
BackEnd 100

mongod

Linux / Mac

service mongod start


ou
brew services start mongodb

O próximo passo é rodar a aplicação para checar se não há erro. Existem duas formas:

1. Executando o arquivo app.js, diretamente com node


• node app.js
2. Executando o comando npm start
• npm start

A segunda maneira é a mais indicada porque ela gera configurações relacionadas à portas e
ambientes.

Criando web API


O objetivo dessa API é buscar os dados já cadastrados no banco e fornecer estes dados formatados
para quem a acessar.
No projeto inicial temos duas rotas cadastradas, inicialmente. Faremos algumas alterações. Veja as
rotas cadastradas no arquivo api/app.js:

1 app.use('/', index);
2 app.use('/users', users);

A primeira rota aponta para o arquivo routes/index.js e a segunda para routes/users.js.


A alterção que faremos na estrutura inicial, consiste em remover estas rotas e carregamentos iniciais.
Veja as alterações:
Remover:
BackEnd 101

1 var index = require('./routes/index');


2 var users = require('./routes/users');
3
4 app.use('/', index);
5 app.use('/users', users);

Adicionar:

1 require('./routes')(app);

Estamos carregando o arquivo routes.js que está na pasta raiz da API. Como este arquivo ainda não
existe, crie e adicione o seguinte código:

1 module.exports = (app) => {


2 app.use('/users', require('./routes/users'))
3 app.use('/rooms', require('./routes/rooms'))
4 }

Assim:

1. Registramos as rotas e fizemos o carregamento dos arquivos, diretamente.


2. Carregamos os arquivos dentro das pastas routes/users e routes/rooms, que ainda não
existem.
3. Como não estamos passando nome de nenhum arquivo, automaticamente será carregado o
arquivo index.js, dentro de cada uma destas pastas.
4. Apague os arquivos originais, que estão dentro da pasta routes
5. Crie as pastas acima e um arquivo index.js, dentro de cada uma delas.

Conteúdo do arquivo routes/users/index.js e


routes/rooms/index.js
1 var express = require('express');
2 var router = express.Router();
3
4 router.get('/', require('./find'))
5
6 module.exports = router

O conteúdo dos dois arquivos são exatamente iguais, para este momento.
Crie os arquivos routes/users/find.js e routes/rooms/find.js. Quando o usuário acessar a rota raiz,
este arquivo que será chamado, conforme código acima.
BackEnd 102

Chegamos a um ponto que precisaremos utilizar o mongoose, para manipular os dados no banco, e
precisamos de um schema. Ainda não criamos na API estes models, mas já temos criado na seção
de admin.
Replicaremos estes mesmos models. Faremos com que possam retornar os valores no formato de
uma linguagem universal.
Crie uma pasta chamada model, dentro de sua pasta raiz, ou seja, no mesmo nível que a pasta routes.
Dentro desta pasta, crie dois arquivos: users.js e rooms.js.
Depois disso, copie os dados do model da seção de admin e cole nestes arquivos. Veja abaixo:

1 // users.js
2 var mongoose = require('mongoose')
3 //const passportLocalMongoose = require('passport-local-mongoose')
4
5 const User = new mongoose.Schema({
6 name: {
7 type: String,
8 required: true
9 },
10 slug: {
11 type: String,
12 required: true
13 },
14 email: {
15 type: String,
16 required: true
17 },
18 password: {
19 type: String,
20 required: true
21 }
22 })
23
24 //User.plugin(passportLocalMongoose, { usernameField: 'email' })
25
26 module.exports = mongoose.model('User', User);
BackEnd 103

1 // rooms.js
2 const mongoose = require('mongoose')
3
4 const Rooms = new mongoose.Schema({
5 name: {
6 type: String,
7 required: true
8 },
9 slug: {
10 type: String,
11 required: true
12 },
13 description: {
14 type: String,
15 required: true
16 },
17 enable: {
18 type: Boolean,
19 required: true,
20 default: true
21 },
22 users: [{
23 type: mongoose.Schema.Types.ObjectId,
24 ref: 'Users'
25 }],
26 created: {
27 type: Date,
28 required: true,
29 default: new Date()
30 }
31 })
32
33 module.exports = mongoose.model('Rooms', Rooms)

Com os models criados, basta criarmos os arquivos find.js, que serão responsáveis por retornar os
dados encontrados no banco, em formato json.
Conteúdo do arquivo **routes/users/find.js **
BackEnd 104

1 var Users = require('./../../model/users');


2
3 module.exports = (req, res) => {
4 Users
5 .find({})
6 .then((users) => {
7 if(!users){
8 return res
9 .status(404)
10 .json({
11 status: false,
12 users
13 })
14 }
15
16 return res
17 .status(200)
18 .json({
19 status: true,
20 users
21 })
22 })
23 .catch((error) => {
24 return res
25 .status(500)
26 .json({
27 status: false,
28 error
29 })
30 })
31 }

Conteúdo do arquivo **routes/rooms/find.js**


BackEnd 105

1 var Rooms = require('./../../model/rooms');


2
3 module.exports = (req, res) => {
4 Rooms
5 .find({})
6 .then((rooms) => {
7 if(!rooms){
8 return res
9 .status(404)
10 .json({
11 status: false,
12 rooms
13 })
14 }
15
16 return res
17 .status(200)
18 .json({
19 status: true,
20 rooms
21 })
22 })
23 .catch((error) => {
24 return res
25 .status(500)
26 .json({
27 status: false,
28 error
29 })
30 })
31 }

Criamos os endpoints, os models e estamos com o mongoose funcionando. Basta acessarmos as urls
abaixo e já teremos os resultados retornados:
http://localhost:3000/users
http://localhost:3000/rooms
Se precisarmos de mais rotas, para buscar dados diferentes, criaremos no decorrer do conteúdo.
O mais importante é que a API está criada e retornando dados que precisamos.
BackEnd 106

Criando Servidor Socket


Criamos a Web API, agora chegou a hora de trabalharmos com o protocolo web sockets.
Para este procedimento, utilizaremos o socket.io, como havíamos dito anteriormente. O projeto
socket.io tem como função abstrair a parte de conexão e envio de dados, de uma forma bem simples
de trabalhar.
Criaremos o servidor web socket e mostraremos como podemos interagir com ele.
Faremos algumas alterações no projeto atual da API, mas antes disso teremos que instalar o pacote
socket.io.

npm install socket.io --save

Após a instalação do pacote, começaremos a fazer as alterações e iniciaremos pelo arquivo api/app.js.

1 // Código já existente
2 var app = express();
3
4 // Código adicionado
5 var server = require('http').Server(app);
6 var io = require('socket.io')(server);
7
8 // Código alterado no final do arquivo (altere)
9 module.exports = app;
10
11 module.exports = {
12 app: app,
13 server: server
14 };

Criamos o servidor HTTP e o servidor socket.io, diretamente no arquivo app.js. Anteriormente, o


servidor HTTP era criado no arquivo ./bin/www, ao rodarmos o comando npm start.
Antes, o arquivo app.js só exportava uma instância app. Agora, exportamos um objeto com duas
propriedades: app e server.

Arquivo ./bin/www.js
BackEnd 107

1 // Alteração para variável app


2 var app = require('../app');
3
4 var app = require('../app').app;
5
6 // Alteração para variável server
7 var server = http.createServer(app);
8
9 var server = require('../app').server;

Alteramos a forma de carregar as duas variáveis acima, faça estas alterações.


Concluídas as alterações, acrescentaremos a cada resposta de requisição, o servidor socket.io.
Faremos isso criando um middleware. Veja o código do arquivo app.js:

1 // já existente
2 mongoose.connect('mongodb://127.0.0.1:27017/chatschool_dev');
3
4 // código adicionado
5 app.use((req, res, next) => {
6 res.io = io;
7 next();
8 });
9
10 // já existente
11 require('./routes')(app);

Após adicionar este código, reinicie o servidor com npm start e já estará tudo pronto para continuar
o desenvolvendo com os servidores criados e aptos a retornar dados, utilizando o protocolo socket.io.
Já podemos começar a interagir, em tempo real, junto com o servidor próprio de websocket e também
webserver.
Criando Comunicação Via Socket
Enviando Mensagens
Já construímos a conexão junto ao servidor e começaremos a consumir os dados dentro da webapp.
A partir de agora utilizaremos o pacote socket.io para conseguirmos a conexão junto ao servidor de
websocket.
Em primeiro lugar faremos alguns carregamentos de scripts no arquivo webapp/src/index.html.
Veja abaixo:

1 <!-- Carregamentos existentes -->


2 <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></sc\
3 ript>
4 <script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/boots\
5 trap.min.js"></script>
6
7 <!-- Carregamentos adicionados -->
8 <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/1.7.3/socket.io.js"></\
9 script>
10 <script src="./js/app.js"></script>

Carregamos o script do socket.io via CDN e um arquivo chamado app.js, este arquivo ainda não
existe. O carregamento do socket.io é necessário para conseguirmos conectar ao servidor de API
juntamente com o arquivo app.js.
Dentro da pasta webapp/src/js, crie um arquivo chamado app.js. Veja o conteúdo do arquivo:

1 var socket = io('//localhost:3000')


2
3 // socket.emit()

Por enquanto, deixaremos o evento socket.emit() comentado para testarmos se a conexão será bem
sucedida. Depois, enviaremos uma mensagem do frontend para o servidor.
Para fazermos o teste de conexão temos que preparar o reconhecimento do evento no arquivo
api/app.js. Veja o código adicionado:
Criando Comunicação Via Socket 109

1 // Código existente
2 app.use((req, res, next) => {
3 res.io = io
4 next()
5 })
6
7 // Código adicionado
8 io.on('connection', function () {
9 console.log('A new connection has been established')
10 })

Após adicionarmos este código, o servidor ficará aguardando uma conexão e quando o webapp
conectar ao servidor, pela url http://localhost:3000, o servidor dispara a mensagem no console.
O que precisamos fazer é executar a webapp através do gulp e depois rodar o servidor, novamente.
Veja os passos:

1. Acesse a pasta raiz de webapp e rode o comando gulp.


2. Acesse a pasta raiz da API e rode o comando npm start.

Como resposta você terá:

api@0.0.0 start /Users/mac/www/chatschool/api


node ./bin/www
A new connection has been established

Se conseguirmos obter esta mensagem, significa que conseguimos trabalhar com esta conexão,
enviando e recebendo dados.
O próximo passo será enviarmos uma mensagem do usuário, que conectou no webapp, para o
servidor de API.
Precisaremos atribuir um id para o formulário presente no arquivo webapp/src/index.html.

1 <div class="col-xs-12 type">


2 <form>
3 <input type="text" id="message" class="form-control" name="message" placehol\
4 der="Type your message here">
5 </form>
6 </div>

Após receberemos o valor digitado através do seletor jQuery, o enviaremos através do comando
emit, utilizando o socket.io. Para isso altere o arquivo webapp/src/js/app.js.
Criando Comunicação Via Socket 110

1 var socket = io('//localhost:3000')


2
3 $('#message').keypress(function (e) {
4 if(e.which == 13){
5 var val = $('#message').val()
6
7 socket.emit('message', {
8 message: val
9 })
10
11 return false
12 }
13 })

No código acima, pegamos o evento de digitação no formulário. Quando o evento reconhecer um


enter, que se trata do número 13 na tabela de códigos, teremos o evento de mensagem enviado ao
servidor.
Após o envio devemos receber o valor no servidor e retornar. Veja o código adicionado no arquivo
api/app.js.

1 io.on('connection', function (socket) {


2 console.log('A new connection has been established')
3
4 socket.on('message', function (data) {
5 console.log(data)
6 })
7 })

Observem que o comando emit possui um identificador message e o evento on deve receber o
mesmo identificador, caso contrário a comunicação não será concluída.
Agora, basta fazer o teste de digitar uma mensagem no formulário e pressionar enter. Em seguida,
acesse o terminal e confira se recebeu a mensagem digitada. Caso tenha recebido, é porque já possui
uma conexão de sucesso e já consegue receber dados do frontend, no servidor.
Ao pressionar a tecla enter no fomulário, deverá receber o seguinte resultado no terminal:

api@0.0.0 start /Users/mac/www/chatschool/api


node ./bin/www

A new connection has been established


{ message: 'Teste' }
{ message: 'Teste 1' }
Criando Comunicação Via Socket 111

{ message: 'Teste 2' }

Recebendo Mensagens
Com o envio de dados do frontend para o servidor funcionando, faremos o processo inverso,
tornando a comunicação de duas vias.
Receberemos a mensagem no servidor e a retornaremos, sem tratativa nenhuma. Poderíamos
submeter esta mensagem a alguma lógica, antes de retorná-la ao frontend.
Faremos o mesmo processo de emissão de mensagem utilizando o emit, só que agora do servidor
para o frontend. Veja a alteração no arquivo api/app.js:

1 io.on('connection', function (socket) {


2 console.log('A new connection has been established')
3
4 socket.on('message', function (data) {
5 console.log(data)
6 })
7
8 socket.on('message', function (data) {
9 console.log(data)
10 socket.emit('message', {
11 message: data.message
12 })
13 })
14 })

Observe que estamos recebendo normalmente o evento de mensagem, assim como anteriormente,
só que gora emitimos a mesma mensagem para o frontend. O método responsável por isso é o
socket.emit, em que passamos um objeto com a propriedade message.

Recebendo mensagem no frontend


Criando Comunicação Via Socket 112

1 // Arquivo webapp/src/js/app.js
2 socket.on('message', function (data) {
3 var template = '<div class="col-xs-12 message">' +
4 ' <div class="avatar col-xs-6 col-md-1">' +
5 ' <h2>L</h2>' +
6 ' </div>' +
7 ' <p class="text col-xs-6 col-md-11">'+ data.message +'</p>' +
8 '</div>'
9
10 $('.conversation').append(template)
11 })

Veja que criamos um template igual ao que temos no arquivo index.html para adicionarmos as
mensagens retornadas pelo servidor.
O evento que recebe a mensagem é o mesmo que recebe no servidor, ou seja, socket.on.
Reinicie o servidor API para que as alterações sejam efetivadas e depois, teste o envio de mensagens.
Após teclar enter, a mensagem vai até o servidor que, por sua vez retorna a mesma mensagem para
o frontend. Recebemos e adicionamos um elemento completo de html, que trata-se do template e
adicionamos a classe conversation, em que ficam as outras mensagens adicionadas, anteriormente,
no layout.
Teste o envio de mensagens após reiniciar o servidor e verá que já temos o envio e o recebimento de
mensagens entre o servidor e frontend.
Comunicação em Grupo
Liberando CORS
Ensinaremos como liberar o Cross-Domain para garantir maior segurança no browser, visando
evitar que todos os domínios tenham acesso a algum ponto da aplicação.
Para trabalhar entre domínios diferentes é necessário fazer essa liberação, porque isso já vem
habilitado no header por padrão .
Estamos trabalhando com três projetos totalmente distintos, que são: admin, api e webapp.
Precisamos que a api se comunique com o webapp, ou seja, envie dados para o frontend e esta
comunicação será feita via ajax. Para este processo o express tem uma forma muito simples de
trabalhar, utilizando um pacote chamado CORS. Com este pacote, conseguimos fazer a liberação do
cross-domain de uma forma muito simples.
Basta instalarmos este pacote na API e ela estará disponível para todos os ambientes, ou seja, admin
e webapp.

Baixando e instalando pacote

Comando Descrição
npm install cors –save Instalando pacote com npm
var cors = require(‘cors’); Adicionando carregamento do pacote
app.use(cors()); Adicionando middleware

Veja como ficou o trecho do código:

1 var mongoose = require('mongoose');


2 var cors = require('cors');
3
4 app.use(cookieParser());
5 app.use(cors());

O cors é um middleware como qualquer outro e deverá ser utilizado e aplicado como tal. Utilizando-
o como um middleware, liberamos o acesso para todo e qualquer domínio e logo depois já
conseguiremos executar o ajax de uma forma tranquila e sem erros de cross-domain.
Você poderia limitar os tipos de métodos permitidos, passando um objeto de configuração dentro
do método cors(). Podemos, também, limitar as urls que seriam acessíveis, mas em nosso caso
Comunicação em Grupo 114

aceitaremos todos os tipos de métodos. Portanto, não passaremos nenhum objeto como parâmetro,
como pode ver no código acima.
A configuração padrão de instalação é suficiente para o projeto. Por este motivo, não passaremos
nenhuma configuração extra para o pacote, apenas o utilizaremos como um middleware padrão.

Mostrando Grupos
Começaremos a capturar os dados da API para exibi-los na aplicação. Deste modo, poderemos
prosseguir com o objetivo que é enviar e receber mensagens no chat.
Antes de continuarmos, teremos que fazer algumas alterações no arquivo webapp/src/js/app.js.
Veja as alterações:

1 $(document).ready(function () {
2
3 (function () {
4 var getRooms = function () {
5 return $.get('//localhost:3000/rooms', function (data) {
6 if(!data.status){
7 return
8 }
9
10 var rooms = data && data.rooms
11
12 var titleTpl = '<li class="list-group-item title">' +
13 ' <h4>Canais(' + rooms.length + ')</h4>' +
14 '</li>'
15
16 $('.channels').append(titleTpl)
17
18 rooms.forEach(function (room, index) {
19 var roomTpl = '<li class="list-group-item" channel="' + room._i\
20 d + '"><i class="fa fa-comment-o"></i> ' + room.name + '</li>'
21 $('.channels').append(roomTpl)
22 })
23 })
24 }
25
26 getRooms()
27 })()
28
29 var socket = io('//localhost:3000')
Comunicação em Grupo 115

30
31 $('#message').keypress(function (e) {
32 if(e.which == 13){
33 var val = $('#message').val()
34
35 socket.emit('message', {
36 message: val
37 })
38
39 return false
40 }
41 })
42
43 socket.on('message', function (data) {
44 var template = '<div class="col-xs-12 message">' +
45 ' <div class="avatar col-xs-6 col-md-1">' +
46 ' <h2>L</h2>' +
47 ' </div>' +
48 ' <p class="text col-xs-6 col-md-11">'+ data.message +'</p>' +
49 '</div>'
50
51 $('.conversation').append(template)
52 })
53
54 })

A primeira alteração que fizemos foi adicionar os métodos anteriores dentro de uma função que só
será executada assim que o documento estiver carregado completamente.
Para isso, utilizamos a estrutura abaixo:

1 $(document).ready(function () {
2
3 })

Mova todo conteúdo anterior para dentro desta função.


A segunda alteração foi criar uma função autoexecutável para gerar as salas dinamicamente,
pegando os dados da API. Pegamos a quantidade real de salas cadastradas na parte administrativa
e listamo-nas com os nomes reais. Não se trata mais de conteúdo HTML de teste.
Para isso, criamos a função abaixo:
Comunicação em Grupo 116

1 (function () {
2 var getRooms = function () {
3 return $.get('//localhost:3000/rooms', function (data) {
4 if(!data.status){
5 return
6 }
7
8 var rooms = data && data.rooms
9
10 var titleTpl = '<li class="list-group-item title">' +
11 ' <h4>Canais(' + rooms.length + ')</h4>' +
12 '</li>'
13
14 $('.channels').append(titleTpl)
15
16 rooms.forEach(function (room, index) {
17 var roomTpl = '<li class="list-group-item" channel="' + room._id + \
18 '"><i class="fa fa-comment-o"></i> ' + room.name + '</li>'
19 $('.channels').append(roomTpl)
20 })
21 })
22 }
23
24 getRooms()
25 })()

Criamos a função getRooms e a chamamos no final da execução. Assim, esta função é executada a
cada requisição, trazendo os dados atualizados de cada sala.
Para pegarmos os dados das salas, acessamos a API. Veja abaixo o método e a url de acesso:

1 return $.get('//localhost:3000/rooms', function (data) {


2 if(!data.status){
3 return
4 }
5
6 var rooms = data && data.rooms
7
8 var titleTpl = '<li class="list-group-item title">' +
9 ' <h4>Canais(' + rooms.length + ')</h4>' +
10 '</li>'
11
12 $('.channels').append(titleTpl)
Comunicação em Grupo 117

13
14 rooms.forEach(function (room, index) {
15 var roomTpl = '<li class="list-group-item" channel="' + room._id + '"><i cl\
16 ass="fa fa-comment-o"></i> ' + room.name + '</li>'
17 $('.channels').append(roomTpl)
18 })
19 })

Esta função testa se existe o atributo status na requisição, pois se algo der errado paramos a execução.
Se a requisição obtiver sucesso, continuamos a execução e confeccionamos os valores retornados em
uma variável chamada rooms.
Logo depois, pegamos o template de listagem, presente no arquivo webapp/src/index.html, para
que possamos adicionar o título de canais utilizando a mesma estrutura. Guardamos este template
em uma variável chamada titleTpl e, em seguida, adicionamos o elemento dinamicamente dentro
do bloco que contém a classe channels. Desta foma, temos o título e a quantidade de salas de forma
dinâmica. Se adicionarmos mais salas, o título mostrará a quantidade exata de salas criadas. No
HTML este número de salas era fixo.
Fizemos o mesmo para a listagem de salas.
Pegamos o template do HTML e transportamos para uma variável chamada roomTpl e esta estrutura
é repetida em um forEach para que sejam impressas todas as salas cadastradas no banco de dados,
com os nomes reais de cadastro.
Achamos necessário adicionar um atributo chamado channel com o id de cada sala, para podermos
diferenciar uma sala da outra. Este processo será útil para que cada sala tenha identidade única.
Você pode utilizar somente um atributo personalizado chamado channel, como fizemos:

1 var roomTpl = '<li class="list-group-item" channel="' + room._id + '"><i class="fa \


2 fa-comment-o"></i> ' + room.name + '</li>'

Ou, pode utilizar o padrão recomendado pela W3C, em que adicionamos atributos personalizados
utilizando o prefixo data-. Ficaria da seguinte forma:

1 var roomTpl = '<li class="list-group-item" data-channel="' + room._id + '"><i class\


2 ="fa fa-comment-o"></i> ' + room.name + '</li>'

Em ambas as formas, teremos o mesmo resultado.


Não esqueça de remover os elementos do arquivo webapp/src/index.html, pois eles serão gerados
dinamicamente pela função. Basta deixar o bloco que contém a classe channel sem conteúdo algum.
Veja o código abaixo:
Comunicação em Grupo 118

1 <ul class="list-group channels"></ul>

Após realizar esta alteração, você estará pronto para continuar o desenvolvimento.
Recarregue a página do chat para conferir os dados das salas vindos do banco de dados. Inspecione
o elemento HTML na ferramenta de desenvolvedor do navegador, para conferir o id de cada sala
sendo impresso em seu atributo.

Mensagens em Grupo
Refatoraremos o código e utilizaremos a base que já temos de desenvolvimento, para finalizarmos
esta parte de mensagens em grupo.
O primeiro passo para que alguém possa enviar uma mensagem dentro de uma determinada sala será
utilizar a feature do socket.io que se chama socket.room. No momento, as mensagens são enviadas
mas aparecem em qualquer sala, pois ainda não definimos esta separação.
Em qualquer projeto em que haja distinção de sala ou setor, teremos que utilizar uma ferramenta
para fazer a separação. Assim, a aplicação sabe para qual sala estamos enviando a mensagem, caso
contrário a mensagem não teria um destino correto. Utilizaremos socket.room para este fim.
Para conseguirmos diferenciar uma sala da outra já adicionamos, anteriormente, um identificador
através de um atributo personalizado. Informaremos este id para o servidor assim que o usuário
clicar em algum item da listagem de salas. O servidor, por sua vez, se encarregará de fazer com que
o usuário acesse esta sala e possa fazer o envio de mensagens.
O primeiro passo para fazermos esta comunicação e conexão com a sala via id é criando o evento
de click no servidor. Adicione o código abaixo ao arquivo webapp/src/js/app.js.

1 // Código existente
2 var socket = io('//localhost:3000')
3
4 $('.channels').on('click', '.channel', function(){
5 var roomId = $(this).attr('channel')
6
7 console.log(roomId)
8
9 socket.emit('join room', {
10 room: roomId
11 })
12
13 return false
14 })

Veja que estamos assistindo um evento de click em uma classe chamada channel. Porém esta classe
ainda não está no código gerado pelo loop das salas. Portanto, adicione conforme o código abaixo.
Comunicação em Grupo 119

1 rooms.forEach(function (room, index) {


2 var roomTpl = '<li class="list-group-item channel" channel="' + room._id + '"><\
3 i class="fa fa-comment-o"></i> ' + room.name + '</li>'
4 $('.channels').append(roomTpl)
5 })

Agora, sempre que clicarmos na classe channel, teremos o evento emitido para o servidor.
Criamos uma variável chamada roomId e atribuímos o valor do atributo personalizado. Neste caso
é o id da sala que está vindo do MongoDB.
Veja como acompanhar o processo:

1. logamos no console o valor do id capturado. Se clicar, poderá ver o evento ser disparado.
2. emitimos um evento via socket, em que passamos um objeto room com o id da sala.

Para recebermos este evento no servidor, teremos que criar o evento receptor. Abra o arquivo
api/app.js e adicione o seguinte código:

1 var sockets = io.sockets


2
3 sockets.on('connection', function (socket) {
4 console.log('A new connection has been established')
5
6 socket.on('message', function (data) {
7 console.log(data)
8 socket.emit('message', {
9 message: data.message
10 })
11 })
12
13 socket.on('join room', function (data) {
14 socket.room = data.room
15 socket.join(socket.room)
16
17 socket.emit('joined room', data)
18 })
19 })

O evento connection utiliza a instância socket diretamente, para disparar os eventos de comunica-
ção.
O próximo passo é recebermos o evento join room, que enviamos da aplicação webapp.
Comunicação em Grupo 120

1 // Código existente
2 var socket = io('//localhost:3000')
3
4 socket.on('join room', function (data) {
5 socket.room = data.room
6 socket.join(socket.room)
7
8 socket.emit('joined room', data)
9 })

Observe que estamos recebendo e atribuindo uma função callback para atribuir uma propriedade
room ao objeto socket, onde passamos o valor do id, que passamos no evento anterior. O id está
dentro de data.room.
Depois, utilizamos o método join para acessarmos a sala, passando o id que acabamos de receber.
O próximo passo é enviar um outro evento, informando o webapp que conseguimos acessar a sala.

1 socket.emit('joined room', data)

Enviamos um evento, chamado joined room, passando todos os dados processados em uma variável
data.
Agora, temos que receber este evento novamente, em webapp.

1 // Código existente
2 var socket = io('//localhost:3000')
3
4 // código adicionado
5 var currentRoom = undefined
6
7 // Códico existente
8 $('#message').on('keypress', function (e) {
9 // código
10 })
11
12 // código adicionado
13 socket.on('joined room', function (data) {
14 currentRoom = data.room
15 console.log(currentRoom)
16 })

Veja o que fizemos:


Comunicação em Grupo 121

1. criamos uma variável currentRoom indefinida


2. dentro do evento atribuímos a variável currentRoom com o valor da sala que o usuário acessou
3. logamos novamente no console

Se clicar agora na sala, terá duas vezes o mesmo id logado.


Caso não tenha nenhuma alteração, reinicie o servidor.
Já temos o acesso da sala identificado pelo servidor. Armazenaremos todos os dados correntes na
variável currentRoom, para processarmos o restante das informações e mensagens e direcionarmos
as ações que quisermos.
Trabalharemos com broadcast agora que já temos acesso a uma sala específica. Desta forma,
conseguiremos enviar e receber mensagens.
Utilizaremos o mesmo código de envio de mensagens já criado, para acelerar o desenvolvimento e
alteraremos alguns pontos.
Todo e qualquer evento de socket, colocaremos abaixo dos eventos jQuery, para organizarmos
melhor o código. Veja como ficará o arquivo webapp/src/js/app.js, depois das alterações:

1 $(document).ready(function () {
2
3 (function () {
4 var getRooms = function () {
5 return $.get('//localhost:3000/rooms', function (data) {
6 if(!data.status){
7 return
8 }
9
10 var rooms = data && data.rooms
11
12 var titleTpl = '<li class="list-group-item title">' +
13 ' <h4>Canais(' + rooms.length + ')</h4>' +
14 '</li>'
15
16 $('.channels').append(titleTpl)
17
18 rooms.forEach(function (room, index) {
19 var roomTpl = '<li class="list-group-item channel" channel="' +\
20 room._id + '"><i class="fa fa-comment-o"></i> ' + room.name + '</li>'
21 $('.channels').append(roomTpl)
22 })
23 })
24 }
Comunicação em Grupo 122

25
26 getRooms()
27 })()
28
29 var socket = io('//localhost:3000')
30 var currentRoom = undefined
31
32 // Events jQuery
33 $('.channels').on('click', '.channel', function(){
34 var roomId = $(this).attr('channel')
35
36 console.log(roomId)
37
38 socket.emit('join room', {
39 room: roomId
40 })
41
42 return false
43 })
44
45 $('#message').on('keypress', function (e) {
46 if(e.which == 13 || e.keyCode == 13){
47 var message = $('#message').val()
48
49 if(!message){
50 return
51 }
52
53 socket.emit('message room', {
54 message: message,
55 room: currentRoom
56 })
57
58 var msgTpl = '<div class="col-xs-12 message">' +
59 ' <div class="avatar col-xs-6 col-md-1">' +
60 ' <h2>L</h2>' +
61 ' </div>' +
62 ' <p class="text col-xs-6 col-md-11">'+ message +'</p>\
63 ' +
64 '</div>'
65
66 $('.conversation').append(msgTpl)
67 $('#message').val('')
Comunicação em Grupo 123

68
69 return false
70 }
71 })
72
73 // Events sockets
74 socket.on('joined room', function (data) {
75 currentRoom = data.room
76 console.log(currentRoom)
77 })
78
79 socket.on('messaged room', function (data) {
80 if(!data.message){
81 return
82 }
83
84 var msgTpl = '<div class="col-xs-12 message">' +
85 ' <div class="avatar col-xs-6 col-md-1">' +
86 ' <h2>L</h2>' +
87 ' </div>' +
88 ' <p class="text col-xs-6 col-md-11">'+ data.message +'</p\
89 >' +
90 '</div>'
91
92 $('.conversation').append(msgTpl)
93 })
94 })

Alteramos o evento de envio de mensagem para o código abaixo:

1 $('#message').on('keypress', function (e) {


2 if(e.which == 13 || e.keyCode == 13){
3 var message = $('#message').val()
4
5 if(!message){
6 return
7 }
8
9 socket.emit('message room', {
10 message: message,
11 room: currentRoom
12 })
13
Comunicação em Grupo 124

14 var msgTpl = '<div class="col-xs-12 message">' +


15 ' <div class="avatar col-xs-6 col-md-1">' +
16 ' <h2>L</h2>' +
17 ' </div>' +
18 ' <p class="text col-xs-6 col-md-11">'+ message +'</p>' +
19 '</div>'
20
21 $('.conversation').append(msgTpl)
22 $('#message').val('')
23
24 return false
25 }
26 })

A primeira alteração foi na condicional:

1 if(e.which == 13 || e.keyCode == 13){ }

O wich funciona na maioria dos navegadores, mas no Internet Explorer teríamos problema.
Adicionamos o keyCode para que todos os navegadores reconheçam o evento.
Depois, verificamos se existe mensagem. Se não tiver mensagem, interrompemos a operação, para
evitarmos o envio de mensagens vazias sem resultado algum para o servidor.

1 if(!message){
2 return
3 }

Após a verificação, emitimos um evento chamado message room, que anteriormente se chamava
message, passando a mensagem capturada no campo input e também a sala corrente.

1 socket.emit('message room', {
2 message: message,
3 room: currentRoom
4 })

Depois, criamos o template e adicionamos a mensagem na tela do usuário que enviou e limpamos o
campo de texto para que possa ser adicionada outra mensagem.
Comunicação em Grupo 125

1 var msgTpl = '<div class="col-xs-12 message">' +


2 ' <div class="avatar col-xs-6 col-md-1">' +
3 ' <h2>L</h2>' +
4 ' </div>' +
5 ' <p class="text col-xs-6 col-md-11">'+ message +'</p>' +
6 '</div>'
7
8 $('.conversation').append(msgTpl)
9 $('#message').val('')

Recebendo evento no servidor


Agora que emitimos o evento para o servidor, temos que receber e retornar um evento para o webapp.
Primeiro, veja como ficou o código do arquivo api/app.js referente aos eventos de socket:

1 var sockets = io.sockets


2
3 sockets.on('connection', function (socket) {
4 console.log('A new connection has been established')
5
6 socket.on('message room', function (data) {
7 socket.broadcast.in(data.room).emit('messaged room', {
8 message: data.message,
9 room: data.room
10 })
11 })
12
13 socket.on('join room', function (data) {
14 socket.room = data.room
15 socket.join(socket.room)
16
17 socket.emit('joined room', data)
18 })
19 })

Recebemos o evento message room e disparamos uma resposta com broadcast, que na verdade
disparará o mesmo retorno para todas as salas de mesmo acesso. Todos os usuários que estiverem
na mesma sala, receberão a mensagem ao mesmo tempo.
Veja como fizemos:

1. utilizamos o método broadcast junto com o método in, em que passamos o id da sala
Comunicação em Grupo 126

2. disparamos o evento emit com o nome de message room com a mensagem e a sala.

O processo está, praticamente, completo. Falta receber este evento, novamente, no webapp cujo
código já foi adicionado acima:

1 socket.on('messaged room', function (data) {


2 if(!data.message){
3 return
4 }
5
6 var msgTpl = '<div class="col-xs-12 message">' +
7 ' <div class="avatar col-xs-6 col-md-1">' +
8 ' <h2>L</h2>' +
9 ' </div>' +
10 ' <p class="text col-xs-6 col-md-11">'+ data.message +'</p>' +
11 '</div>'
12
13 $('.conversation').append(msgTpl)
14 })

Veja que verificamos se a mensagem existe, antes de continuarmos o processo. Caso exista, formamos
o mesmo template anterior e adicionamos dentro da classe conversation. Desta forma, este processo
será executado em todas as salas de mesmo id e, consequentemente, todos receberão a mensagem
enviada.
Veja que o processo com socket geralmente é executado através de um envio, um recebimento e um
retorno.
Já tínhamos feito o mesmo processo, porém, retornávamos apenas para o mesmo usuário que havia
enviado a mensagem. Agora, utilizamos o broadcast para que o retorno seja enviado em cascata,
afetando todos os usuários da mesma sala.
Depois de realizar estas alterações, reinicie os serviços, tanto no servidor quanto a aplicação webapp,
para certificar que está tudo atualizado. Faça testes de envio de mensagens abrindo várias janelas e
cada uma acessando uma sala diferente, para ver os eventos disparados. Acesse salas iguais para ver
os eventos enviados para as salas, em navegadores diferentes.
Com tudo funcionando, você já pode apagar as mensagens que havíamos colocado de teste para o
layout. Veja o código abaixo:
Comunicação em Grupo 127

1 <!-- ANTES -->


2 <div class="col-xs-12 conversation">
3 <div class="col-xs-12 message">
4 <div class="avatar col-xs-6 col-md-1">
5 <h2>L</h2>
6 </div>
7 <p class="text col-xs-6 col-md-11">This is my first message</p>
8 </div>
9 <div class="col-xs-12 message">
10 <div class="avatar col-xs-6 col-md-1">
11 <h2>L</h2>
12 </div>
13 <p class="text col-xs-6 col-md-11">This is my first message</p>
14 </div>
15 <div class="col-xs-12 message">
16 <div class="avatar col-xs-6 col-md-1">
17 <h2>L</h2>
18 </div>
19 <p class="text col-xs-6 col-md-11">This is my first message</p>
20 </div>
21 </div>
22
23 <!-- DEPOIS -->
24 <div class="col-xs-12 conversation">
25
26 </div>

Melhorando usabilidade
Como adicionamos no layout o nome da sala manualmente, alteramos a sala e não temos nenhuma
alteração no layout, nenhuma modificação informando qual sala estamos.
Melhoraremos este processo, adicionando o nome de cada sala ao selecioná-la.
O primeiro passo será adicionar outro atributo personalizado que guardará o nome da sala. No
mesmo lugar que adicionamos o atributo de identificação, adicionaremos mais um atributo, veja
abaixo:
Comunicação em Grupo 128

1 rooms.forEach(function (room, index) {


2 var roomTpl = '<li class="list-group-item channel" name = "'+ room.name +'" cha\
3 nnel="' + room._id + '"><i class="fa fa-comment-o"></i> ' + room.name + '</li>'
4 $('.channels').append(roomTpl)
5 })

Depois, temos que resgatar este valor e enviar para o servidor, assim como fizemos com o id da sala:

1 $('.channels').on('click', '.channel', function(){


2 var roomId = $(this).attr('channel')
3 var roomName = $(this).attr('name')
4
5 socket.emit('join room', {
6 room: roomId,
7 roomName: roomName
8 })
9
10 return false
11 })

Após enviar os dados com o evento join room, não precisaremos alterar o evento de recebimento,
porque ele já retorna todos os dados para o webapp, através do evento joined room.

1 socket.on('join room', function (data) {


2 socket.room = data.room
3 socket.join(socket.room)
4
5 socket.emit('joined room', data)
6 })

O que precisamos fazer é imprimir no HTML o nome da sala, através deste dado enviado. O primeiro
passo é apagar o valor fixo que imprimíamos no HTML, veja abaixo:
Comunicação em Grupo 129

1 <!-- ANTES -->


2 <header class="col-xs-12 header">
3 <span class="username">@leonan</span>
4 </header>
5
6 <!-- DEPOIS -->
7 <header class="col-xs-12 header">
8 <span class="username"></span>
9 </header>

Agora, através da classe username adicionaremos o valor dinamicamente, através do jQuery, no


evento de retorno do servidor joined room.

1 socket.on('joined room', function (data) {


2 currentRoom = data.room
3 $('.username').html('@' + data.roomName)
4 })

Desta forma, adicionamos os valores, dinamicamente. Lembrando que você pode precisar subir os
servidores, novamente, caso não tenham as alterações aplicadas.

Criaremos um botão para sair da sala, já que estamos criando um acesso temos que criar uma saída
desta sala.
Para isso, adicione um botão no HTML logo após o código que adicionamos o nome da sala, abaixo
da classe username.

1 <header class="col-xs-12 header">


2 <span class="username"></span>
3 <button type="button" class="btn btn-default pull-right" id="btn_leave">Sair</bu\
4 tton>
5 </header>

Adicionamos um botão com as classes btn btn-default pull-right, que caracterizam um botão
default do Bootstrap e alinhamos a direita, para que não fique junto com o nome. Além disso,
atribuímos um id para que possamos adicionar eventos a este botão.
Agora, resgataremos o id da sala para enviar para o servidor e efetuarmos a saída da sala, através
de um novo evento que chamaremos de leave room. Adicione ao arquivo
Comunicação em Grupo 130

1 $('#btn_leave').on('click', function(e){
2 var roomId = $(this).attr('channel')
3
4 socket.emit('leave room', {
5 room: roomId
6 })
7
8 return false
9 })

Receberemos dentro do servidor e efetuaremos a saída da sala:

1 socket.on('leave room', function (data) {


2 socket.leave(data.room)
3 socket.room = ''
4
5 socket.emit('leaved room', true)
6 })

Veja o que fizemos:

1. Efetuamos a saída através do método leave e passamos o id da sala


2. Atribuímos a propriedade room, do objeto socket como sendo vazia
3. Enviamos um evento de retorno chamado leaved room.

Como emitimos um novo evento de retorno, temos que receber este evento em webapp.

1 socket.on('leaved room', function (data) {


2 currentRoom = undefined
3 })

Ao recebermos o evento do servidor, apenas setamos a variável currentRoom como undefined


novamente, para não corrermos o risco de continuarmos enviando mensagem para a antiga
currentRoom.

Interagindo com a chatbox


Assim que a aplicação terminar o load normal de carregamento, mostramos um conteúdo na
chatbox, ou seja, o formulário de envio. Porém, não queremos que seja mostrado até que o usuário
clique em alguma sala. Adicionaremos um evento hide na função autoexecutável no arquivo
webapp/src/js/app.js.
Comunicação em Grupo 131

1 (function () {
2 $('.chatbox').hide()
3 var getRooms = function () {
4 // Conteúdo da função
5 }
6
7 getRooms()
8 })()

Ocultamos o conteúdo e temos que adicionar o evento show, para que seja mostrado ao clicar nas
salas. Adicionaremos este método ao evento de retorno de acesso às salas.

1 socket.on('joined room', function (data) {


2 currentRoom = data.room
3 $('.username').html('@' + data.roomName)
4 $('.conversation').html('')
5 $('.chatbox').show()
6 })

Além de mostrar a chatbox, limpamos as mensagens já existentes, para que uma mensagem não seja
mostrada na mudança de uma sala para outra.
Depois, precisamos fazer com que seja ocultado, novamente, quando o usuário clicar no botão Sair.
Adicionaremos o método hide ao evento de retorno de saída das salas.

1 socket.on('leaved room', function (data) {


2 currentRoom = undefined
3 $('.chatbox').hide()
4 $('.conversation').html('')
5 })

Além de ocultar, limpamos todas as mensagens ao clicar no botão sair.


Com todas estas alterações e adaptações, chegamos ao final da conversação em grupo.
Finalizando a Chatbar
Mostrando usuários
Neste capítulo faremos a listagem de usuários.
Seguiremos a mesma lógica da listagem de salas. Adicionaremos uma função na função autoexecu-
tável. A alteração foi feita no arquivo webapp/src/js/app.js.

1 var getUsers = function () {


2 return $.get('//localhost:3000/users', function (data) {
3 if(!data.status){
4 return
5 }
6
7 var users = data && data.users
8
9 users.forEach(function (user, index) {
10 var userTpl = '<li class="list-group-item">' + user.name + '</li>'
11
12 $('.messages').append(userTpl)
13 })
14 })
15 }
16
17 getUsers()

Veja o que foi feito:

1. fizemos uma requisição ajax para o endereço //localhost:3000/users, assim como na listagem
de salas.
2. verificamos se existe a propriedade status no resultado retornado, se não existir, encerramos o
processo.
3. exstindo status, atribuímos o resultado a uma variável chamada users, esta mesma variável
conterá o resultado completo e, também, os resultados de usuários.
4. fizemos um forEach para percorrer todos os resultados do banco e imprimir todos os resultados,
utilizando o template que pegamos do HTML.

Lembrando que o HTML estava fixo e você deverá remover o item, deixando apenas o título,
conforme código abaixo:
Finalizando a Chatbar 133

1 <!-- ANTES -->


2 <ul class="list-group messages">
3 <li class="list-group-item title">
4 <h4>Mensagens</h4>
5 </li>
6 <li class="list-group-item">fabricio</li>
7 </ul>
8
9 <!-- DEPOIS -->
10 <ul class="list-group messages">
11 <li class="list-group-item title">
12 <h4>Mensagens</h4>
13 </li>
14 </ul>

Observe que o forEach está adicionando os elementos através de um append na classe messages.

$('.messages').append(userTpl)

Desta forma, conseguimos listar os usuários vindos do banco.


Você pode adicionar ao título, a quantidade de usuários existentes, assim como fizemos nas salas.
Basta seguir a mesma lógica utilizada em rooms, fazendo as adaptações necessárias. Esta alteração
ficará opcional. No projeto deixaremos sem contagem.

Mensagem por Usuário


Poderíamos desenvolver a mensagem por usuário de diversas maneiras. Como já temos um padrão
desenvolvido para salas, utilizaremos a mesma lógica.
O primeiro passo será adicionarmos as classes e os atributos personalizados na função que gera a
listagem de usuários que criamos, anteriormente. Altere o arquivo webapp/src/js/app.js.

1 users.forEach(function (user, index) {


2 var userTpl = '<li class="list-group-item user" user="' + user._id + '" usernam\
3 e="' + user.name + '">' + user.name + '</li>'
4
5 $('.messages').append(userTpl)
6 })
Finalizando a Chatbar 134

Veja que adicionamos a classe user junto com a classe list-group-item e também, os atributos user
e username. Os valores são concatenados, dinamicamente, através da variável user do forEach.
O próximo passo será criar o evento jQuery ao clicar em cada item da listagem, ou seja, em cada
usuário.

1 $('.messages').on('click', '.user', function () {


2 var user = $(this).attr('user')
3 var username = $(this).attr('username')
4
5 socket.emit('join user', {
6 user: user,
7 username: username
8 })
9
10 return false
11 })

Quando o usuário clicar em algum membro da sala, criamos duas variáveis: user e username. Para
estas variáveis atribuímos o id e o username, que vêm do banco de dados.
Resgatamos os dados, através dos atributos personalizados que criamos.
Em seguida, emitimos um evento via socket, chamado join user, passando um objeto com user e
username. Como você já sabe, quando emitimos um evento temos que recebê-lo em nosso servidor.
Alterando o arquivo api/app.js.

1 socket.on('join user', function (data) {


2 socket.user = data.user
3 socket.join(socket.user)
4
5 socket.emit('joined user', data)
6 })

Ao recebermos os dados, criamos uma propriedade chamada user no objeto socket, passando o id
do usuário que foi passado através da variável data. Em seguida, acessamos o usuário através do
método join, passando o id do usuário como parâmetro.
Depois, emitimos um outro evento como resposta, chamado joined user. Receberemos este evento
em webapp.
Finalizando a Chatbar 135

1 // Código existente
2 var currentRoom = undefined
3
4 // Código adicionado
5 var currentUser = undefined
6
7 socket.on('joined user', function (data) {
8 currentUser = data.user
9 $('.username').html('@' + data.username)
10 $('.conversation').html('')
11 $('.chatbox').show()
12 })

Veja o que fizemos:

1. setamos o currentUser, passando o id do usuário


2. atribuímos o valor do bloco de username
3. limpamos a conversa e mostramos o bloco para que o usuário possa digitar as mensagens

Tivemos que mostrar o bloco porque, assim como nas rooms, os blocos começam ocultos, inicial-
mente.
Lembre-se de criar a variável currentUser com valor inicial undefined, assim como fizemos para
currentRoom.

Refatorando evento de envio de mensagem


Para utilizarmos o mesmo código de envio de mensagem de grupo, faremos algumas alterações. Veja
abaixo:

1 $('#message').on('keypress', function (e) {


2 if(e.which == 13 || e.keyCode == 13){
3 var message = $('#message').val()
4
5 if(!message){
6 return
7 }
8
9 if(!currentRoom){
10 socket.emit('message user', {
11 message: message,
12 user: currentUser
Finalizando a Chatbar 136

13 })
14 }
15
16 if(currentRoom){
17 socket.emit('message room', {
18 message: message,
19 room: currentRoom
20 })
21 }
22
23 var msgTpl = '<div class="col-xs-12 message">' +
24 ' <div class="avatar col-xs-6 col-md-1">' +
25 ' <h2>L</h2>' +
26 ' </div>' +
27 ' <p class="text col-xs-6 col-md-11">'+ message +'</p>' +
28 '</div>'
29
30 $('.conversation').append(msgTpl)
31 $('#message').val('')
32
33 return false
34 }
35 })

Veja que o código é muito parecido com o anterior, apenas estamos fazendo dois testes lógicos para
saber qual evento emitir.

1. Verificamos se a variável currentRoom é undefined.


2. Se for undefined, significa que se trata de um evento de mensagem entre usuários.
3. Se a variável currentRoom existir, entramos em outro bloco lógico.

Observem que, trata-se de uma mensagem entre usuários. Estamos chamando o evento message
user, caso contrário chamamos o evento message room.
Finalizando a Chatbar 137

1 if(!currentRoom){
2 socket.emit('message user', {
3 message: message,
4 user: currentUser
5 })
6 }
7
8 if(currentRoom){
9 socket.emit('message room', {
10 message: message,
11 room: currentRoom
12 })
13 }

Esta foi a alteração principal neste bloco, o restante continua igual. Vale a pena lembrar que cada
evento está passando a mensagem, que é a mesma para ambos, e o currentUser ou currentRoom, de
acordo com cada contexto.
Como emitimos um evento via socket, temos que receber o evento no servidor, assim como todos os
processos até agora.

1 socket.on('message room', function (data) {


2 socket.broadcast.in(data.room).emit('messaged', {
3 message: data.message,
4 room: data.room
5 })
6 })
7
8 socket.on('message user', function (data) {
9 socket.broadcast.in(data.user).emit('messaged', {
10 message: data.message,
11 user: data.user
12 })
13 })

Repare que ambas executam o evento broadcast, que utilizamos em salas e o código é realmente
muito parecido. Em ambos os casos retornamos um mesmo evento de retorno chamado messaged,
pois utilizamos o mesmo código para envio de mensagem.
Anteriormente, o evento de retorno de rooms se chamava messaged room, basta renomear para
apenas messaged, para reaproveitamento de código.
Para finalizarmos o processo, basta recebermos este evento no webapp.
Finalizando a Chatbar 138

1 socket.on('messaged', function (data) {


2 if(!data.message){
3 return
4 }
5
6 var msgTpl = '<div class="col-xs-12 message">' +
7 ' <div class="avatar col-xs-6 col-md-1">' +
8 ' <h2>L</h2>' +
9 ' </div>' +
10 ' <p class="text col-xs-6 col-md-11">'+ data.message +'</p>' +
11 '</div>'
12
13 $('.conversation').append(msgTpl)
14 })

Note que anteriormente, este evento tinha o identificador messaged room, porque ele servia
apenas para receber o evento de mensagem para salas. Como estamos reaproveitando o código o
renomeamos para messaged, desta forma ele recebe tanto o evento de mensagem para rooms quanto
para users.
Depois de realizar todas estas alterações e adaptações, reinicie os servidores e faça o teste de envio
de mensagens para usuários.
Lembre-se de abrir várias abas do navegador para visualizar o evento broadcast funcionando
corretamente.
Conclusão
O objetivo de criar o chat em tempo real, foi concluído.
Criamos uma aplicação em tempo real, com poucos recursos, utilizando apenas o Node.js, jQuery e
Socket.io.
Este projeto prático, apesar se ser simples, abrangeu diversos conceitos que são utilizados, inclusive,
em projetos de grande escala, como:

• Node.js
• Framework express
• Protocolo HTTP e websocket
• API
• Socket.io
• Template engine handlebars
• Javascript e jQuery

Socket.io nos auxiliou ao trabalharmos com Node, juntamente com o protocolo websocket.
O handlebars é um template engine muito versátil, que nos permite criar helpers, views customizadas
e partials views.
Utilizamos um pouco de javascript, mas focamos mais no uso do jQuery, que apesar de estar um
pouco esquecido, ainda é muito bom para quem está iniciando e nos possibilita fazer tudo que os
outros frameworks mais modernos fazem.
Caso tenha ficado alguma dúvida, deixe as questões no fórum da School of Net, que um de nossos
tutores responderá o mais rápido possível.

Você também pode gostar