Este texto tem como objetivo oferecer uma introdução ao WebSocket, destacando suas capacidades e exemplificando sua implementação na prática com Node.js e Angular.
Particularmente, acredito que uma das formas mais interessantes para estudar o uso de WebSocket é através de um projeto de bate-papo, similar a uma conversa de grupo do Whatsapp. Seguindo a mesma ideia das nostálgicas salas de bate-papo dos anos 2000, e, é claro, com uma boa simplificação, neste texto será apresentado como podemos criar um pequeno bate-papo (chat) com Angular, Node.js e WebSocket.
Funcionalidades básicas de um chat
Em um sistema de bate-papo (chat), as funcionalidades básicas são enviar e receber mensagens. Uma abordagem bem simples para o backend seria criar uma API com endpoints para enviar e receber mensagens:
POST /api/mensagens: envia uma mensagem para alguém.
GET /api/mensagens: retorna as mensagens que o usuário logado recebeu.
Já no frontend teríamos duas funções:
um formulário para o usuário digitar uma mensagem e enviá-la para o servidor.
uma função que é executada de tempos em tempos para buscar as mensagens do servidor. Este processo de requisitar algo a cada intervalo de tempo é conhecido como polling.
O Problema do Pooling
Essa abordagem tradicional, utilizando o protocolo HTTP, apresenta algumas limitações. Suponha que o frontend envie uma requisição ao servidor a cada 5 segundos para verificar se há novas mensagens; isso resulta em 12 requisições por minuto por usuário, independentemente de haver ou não mensagens para ele. Extrapolando o número de usuários para 1000, teremos 12.000 requisições por minuto, mesmo que não haja mensagens para serem recebidas.
Esse processo, conhecido como polling, é ineficiente e gera uma alta carga no servidor. Além disso, as mensagens podem sofrer um atraso considerável, pois o cliente só receberá as novas mensagens no próximo intervalo de polling. Para resolver este problema, é mais adequado utilizar WebSocket ao invés do HTTP, por razões que serão abordadas a seguir.
WebSocket vs HTTP
No tradicional protocolo HTTP, o funcionamento é baseado em requisições e respostas, ou seja, o cliente dispara uma requisição e o servidor entrega uma resposta.
Já com WebSocket, é possível quebrarmos esse paradigma de requisições e respostas. Com ele, estabelecemos um canal de comunicação direto entre o cliente (navegador, aplicativo, etc) e o servidor, permitindo que eles troquem mensagens em tempo real e de forma bidirecional. Isso significa que o servidor pode enviar informações para você sem que você precise solicitar, e você também pode enviar informações para o servidor sem precisar esperar por uma resposta.
Dessa forma, a utilização do WebSocket em um chat é uma abordagem totalmente viável.
WebSocket is a computer communications protocol, providing a simultaneous two-waycommunication channel over a single Transmission Control Protocol (TCP) connection. The WebSocket protocol was standardized by the IETF as RFC 6455 in 2011.
Agora que já abordamos o conceito do WebSocket, vamos partir para a prática e demonstrar como usá-lo na construção de um pequeno chat.
Sobre o projeto de estudo
Para simplificar o projeto, a estrutura do código foi intencionalmente ajustada, e alguns trechos assumem mais responsabilidades do que o ideal. Essa abordagem foi adotada para tornar o projeto o mais enxuto possível, focando em fins didáticos. Além disso, foram adotados alguns requisitos:
A aplicação será executada em um único servidor, não estando preparada para um escalonamento horizontal nem para múltiplas instâncias;
Não serão salvos os históricos das mensagens. Somente a partir do momento que um usuário se conectar na sala, ele passará a receber as mensagens;
As mensagens são visíveis à todos, ou seja, não há mensagens privadas;
As mensagens serão sempre no formato texto;
Não será incluído nenhum meio de autenticação;
Criando o backend do chat com WebSocket e Node.js
Este pequeno projeto foi escrito em TypeScript e utilizou alguns pacotes conforme o script abaixo:
# Versão do Node.js utilizada: 20.18.0
# Inicializando o pacote NPM:
npm init -y
# Instalando as dependências
npm i ws class-validator class-transformer
npm i typescript ts-node @types/ws prettier --save-dev
# Criando o arquivo tsconfig.json
npx tsc --init
# Criando o diretório dos fontes
mkdir src
A lib ws é um pacote para implementação de WebSockets no Nodejs. Já class-validator e class-transformer são pacotes com foco em validação e conversão de objetos. Estes dois últimos, serão utilizados para validar o conteúdo que chega na API.
Após a execução do script acima, os arquivos package.json e tsconfig.json foram ajustados conforme abaixo:
Padronizando as mensagens
Antes de partir para a implementação da lógica em si, vale destacar que após estabelecida uma conexão WebSocket entre cliente e servidor, a comunicação bidirecional se torna possível. Contudo, para garantir a interoperabilidade e a correta interpretação das mensagens, é fundamental estabelecer um padrão de comunicação pré-definido e conhecido por ambas as partes.
Para isto foi adotado a utilização de objetos JSON para encapsular os dados trocados entre o frontend e o backend. Além disto. para representar a estrutura das mensagens JSON, foram criadas classes específicas para mapear os dados de entrada e saída no backend.
No código acima, as marcações com @ são chamadas de decorators, similares às annotations do Java e aos attributes do C#. Nesse caso, os decorators são utilizados para registrar regras de validação, permitindo que os dados recebidos pela API sejam facilmente verificados conforme as regras definidas.
Os dados que chegarão na API serão mapeados para a classe TratarMsgRecebida, que de acordo com o tipo, fará a validação para a classe MsgRecebidaNovoUsuario ou MsgRecebidaTexto.
Da mesma forma que mapeamos classes para representar dados que chegam na API, foram criadas classes para mapear mensagens que são enviadas para o frontend:
A classe Chat, logo abaixo, contém a lógica para receber, processar e enviar mensagens. Deixei alguns comentários para facilitar o entendimento da lógica.
💡
No código a seguir, um delay de 1 segundo foi adicionado às entregas das mensagens, permitindo que, em um ambiente de estudo, seja possível visualizar a confirmação de recebimento pelo servidor.
Finalmente no arquivo index.ts, abrimos a escuta na porta 8080 e sempre que algum cliente se conectar, passamos a responsabilidade para a classe Chat:
Criando o frontend do chat com Angular 18 e Bootstrap
A implementação do frontend ficou bem enxuta, sendo dividida em 2 componentes e 1 service conforme a imagem abaixo ilustra. Além dos pacotes padrão do Angular, somente o pacote Bootstrap foi adicionado para facilitar a construção do layout.
O código fonte ficou da seguinte forma:
💡
Não adicionei o script para criar o projeto, mas no final da página deixei o link do código fonte.
AppComponent:
ChatComponent:
ChatService:
Testando o Chat com WebSocket, Node.js e Angular
Após estruturar os dois projetos, basta executá-los e testar direto no navegador. Veja no GIF abaixo o resultado:
# Backend
npm run start:dev
# Frontend
npm run start
Considerações
Com a possibilidade de comunicação bidirecional no uso do WebSocket, além de um chat, podemos citar vários outros exemplos, como compartilhamento de posição geográfica utilizada por exemplo no Waze e Uber, uso na tela de home brokers onde há uma taxa de atualização de informação muito frequente, jogos online, entre outros.
De forma geral, a ideia desse texto é fornecer o primeiro contato com WebSocket, mostrando sua capacidade e sua implementação na prática. Abaixo deixo um link interessante do ByteByteGo sobre o Design System de um Chat e um outro link falando sobre WebSocket: