Найти в Дзене
drylikov

Создание чат-приложения в режиме реального времени с Vue.js, Socket.IO и Node.js .

В статье я расскажу, как можно создать чат-приложение в режиме реального времени с помощью Vue.js, Node.js, Express и SOCKET.IO. Для работы нам понадобятся Node.js и NPM. Если у вас еще нет установленного Node.JS, то качайте его отсюда. Если все готово, то можно начинать. Создайте директорию для приложения и откройте ее в любимом редакторе. Я пользуюсь Visual Studio Code. Если хотите, можете работать в терминале. mkdir ChatApp && cd ChatApp && code Давайте инициализируем директорию через NPM. npm init Если вам предлагают ввести какую-то информацию, смело ее вводите или нажимайте Enter для настроек по умолчанию. Такая информация используется для настройки пакета .json файла. Давайте установим зависимости приложения. Я покажу список нужных зависимостей и познакомлю с процессом их установки. Чтобы ничего не усложнять, ограничимся двумя зависимостями. i. EXPRESS npm install express --save ii.SOCKET.IO npm install --save socket.io После установки всех зависимостей запустите np
Оглавление

В статье я расскажу, как можно создать чат-приложение в режиме реального времени с помощью Vue.js, Node.js, Express и SOCKET.IO.

Установка.

Для работы нам понадобятся Node.js и NPM. Если у вас еще нет установленного Node.JS, то качайте его отсюда.

  • Вам потребуются базовые знания JavaScript.
  • Пригодятся небольшие знания по Vue.js (но это не принципиально).

Если все готово, то можно начинать.

Создайте директорию для приложения и откройте ее в любимом редакторе. Я пользуюсь Visual Studio Code.

Если хотите, можете работать в терминале.

mkdir ChatApp && cd ChatApp && code

Давайте инициализируем директорию через NPM.

npm init

Если вам предлагают ввести какую-то информацию, смело ее вводите или нажимайте Enter для настроек по умолчанию. Такая информация используется для настройки пакета .json файла.

Установка зависимостей.

Давайте установим зависимости приложения. Я покажу список нужных зависимостей и познакомлю с процессом их установки. Чтобы ничего не усложнять, ограничимся двумя зависимостями.

i. EXPRESS

npm install express --save

ii.SOCKET.IO

npm install --save socket.io

После установки всех зависимостей запустите

npm install

Так установятся все необходимые пакеты.

Фронтенд с Vue.js (разметка).

Интерфейс приложения мы сделаем в Vue.js. Его нужно установить в нашу директорию и добавить bootstrap.

Создадим файл index.html.

touch index.html

Для включения в проекты Vue.js и bootstrap скопируем CDN и добавим в раздел со скриптами файла index.html.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>ChatApp_Socket</title>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous" />

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.js"></script>
<script src="/socket.io/socket.io.js"></script>
</head>

После успешной установки Vue и bootstrap перейдем к созданию разметки.

<body>
<div id="app">
<div class="container">
<div class="col-lg-6 offset-lg-3">
<div v-if="ready">
<p v-for="user in info">
{{user.username}} {{user.type}}
</p>
</div>
<div v-if="!ready">
<h4>Enter your username</h4>
<form @submit.prevent="addUser">
<div class="form-group row">
<input type="text" class="form-control col-9" v-model="username"
placeholder="Enter username here">
<input type="submit" value="Join" class="btn btn-sm btn-info ml-1">
</div>
</form>
</div>
<h2 v-else>{{username}}</h2>
<div class="card bg-info" v-if="ready">
<div class="card-header text-white">
<h4>My Chat App <span class="float-right">{{connections}} connections</span></h4>
</div>
<ul class="list-group list-group-flush text-right">
<small v-if="typing" class="text-white">{{typing}} is typing</small>
<li class="list-group-item" v-for="message in messages">
<span :class="{'float-left':message.type === 1}">
{{message.message}}
<small>:{{message.user}}</small>
</span>
</li>
</ul>
<div class="card-body">
<form @submit.prevent="send">
<div class="form-group">
<input type="text" class="form-control" v-model="newMessage"
placeholder="Enter message here">
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</body>

Для подключения Socket.IO сервера к клиенту добавим клиентские JavaScript библиотеки.

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

Это будет наш файл Vue и bootstrap (HTML) для фронтенда. Можете скопировать код целиком, чтобы не отставать.

Лучший способ научиться чему-то — повторять за другими.

Также можете скачать клиентскую библиотеку Socket.IO.

Можно разграничить функционал, вынеся JavaScript-код из общей разметки. Это решать вам. Но я для удобства этого делать не буду.

Установка сервера.

Создадим server.js. Внутри файла создадим и настроим Express-сервер для работы с Socket.IO.

let app = require('express')();
let http = require('http').Server(app);
let io = require('socket.io')(http);
app.get('/', (req, res) => {
res.sendFile(__dirname + '/index.html')
});


http.listen(3000, () => {
console.log('Listening on port *: 3000');
});

Это базовая конфигурация, необходимая для установки Socket.IO в бэкенде.

Как работает Socket.IO .

Socket.IO — это JavaScript-библиотека чата в режиме реального времени. Документацию можно почитать здесь (она не относится к тематике данной статьи). Я объясню лишь то, что нам потребуется для статьи.

Работа Socket.IO сводится к добавлению слушателей события для экземпляра http.Server. Именно этим мы сейчас и займемся.

let app = require('express')();
let http = require('http').Server(app);
let io = require('socket.io')(http);
app.get('/', (req, res) => {
res.sendFile(__dirname + '/index.html')
});


http.listen(3000, () => {
console.log('Listening on port *: 3000');
});

Затем будем прослушивать событие о новом подключении.

io.on('connection', (socket) => {
socket.on('disconnect', () => {
console.log("A user disconnected");
});

.....

});

Например, если пользователь посещает 127.0.0.1:3000, то сообщение об этом напечатается в консоли.

В Socket.IO есть много флагов или методов, которые можно вызывать для выполнения различных действий (например, отправка событий, прослушивание событий и т.д.).

Socket.ON(): принимает в качестве параметров название события и обратный вызов. Прослушивает события, отправленные на сервер и с него. Обратный вызов нужен для получения данных, связанных с событием.

Socket.EMIT(): отправляет/посылает событие с данными или без них для прослушивания сокетов (включая себя).

Socket.Broadcast.Emit(): отправляет событие всем подключенным клиентам (кроме себя). Эти методы пригодятся для отправки событий с сервера через чат-приложение.

Настройка кода клиентской части.

Откройте index.html. В нижней части файла добавьте следующий код в тег script.

<script>
var socket = io();
let vue = new Vue({
el: '#app',
data: {
newMessage: null,
messages: [],
typing: false,
username: null,
ready: false,
info: [],
connections: 0,
},
created() {
window.onbeforeunload = () => {
socket.emit('leave', this.username);
}

socket.on('chat-message', (data) => {
this.messages.push({
message: data.message,
type: 1,
user: data.user,
});
});
socket.on('typing', (data) => {
this.typing = data;
});


socket.on('stopTyping', () => {
this.typing = false;
});
socket.on('joined', (data) => {
this.info.push({
username: data,
type: 'joined'
});
setTimeout(() => {
this.info = [];
}, 5000);
});
socket.on('leave', (data) => {
this.info.push({
username: data,
type: 'left'
});
setTimeout(() => {
this.info = [];
}, 5000);
});
socket.on('connections', (data) => {
this.connections = data;
});
},
watch: {
newMessage(value) {
value ? socket.emit('typing', this.username) : socket.emit('stopTyping')
}
},
methods: {
send() {
this.messages.push({
message: this.newMessage,
type: 0,
user: 'Me',
});
socket.emit('chat-message', {
message: this.newMessage,
user: this.username
});
this.newMessage = null;
},
addUser() {
this.ready = true;
socket.emit('joined', this.username)
}
},
});
</script>
</html>

Мы добавили var socket = io();и создали новый экземпляр Vue. Далее внутри этого экземпляра прописали наш элемент как el: ‘#app’и объявили наш объект данных с помощью пустых массивов и свойств.

Давайте перейдем к объекту methods. Мы видим методSend(). Он хранит информацию о чате в массиве message и отправляет события чата на сервер с помощью флага Socket.emit().

methods: {
send() {
this.messages.push({
message: this.newMessage,
type: 0,
user: 'Me',
});
socket.emit('chat-message', {
message: this.newMessage,
user: this.username
});
this.newMessage = null;
},

.....

На серверной стороне мы получаем событие с флагом Socket.on() и через флаг Socket.broadcast.emit()пересылаем его другим подключенным клиентам.

В хуке Vue CREATEDмы прослушиваем все события, отправленные сервером. Сюда же включено событие chat-message, которое мы пересылали с сервера ранее.

Через трансляцию событий сервер отправляет их всем подключенным клиентам, кроме самого отправителя.

Аналогия.

Если пользователь А отправляет сообщение на сервер, а сервер ретранслирует его пользователям B, C, D, E и т.д., то это сообщение получат все пользователи, кроме А, поскольку он является отправителем.

Так что нет ничего страшного в том, что мы прослушиваем событие chat-message из объекта CREATED. Все равно мы, как отправители, не получим это сообщение с сервера.

Именно так все и происходит, когда пользователи выполняют какие-то действия в ChatApp.

После получения с сервера события из объекта CREATED , мы сохраняем его в массиве message с message type(указывает, что сообщение отправлено с сервера) и user (тот, кто отправил сообщение).

socket.on('chat-message', (data) => {
this.messages.push({
message: data.message,
type: 1,
user: data.user,
});
});

Подведем итог всему происходящему во фронтенде.

1. Хук Methods. Именно здесь в Vue.js вы создаете все методы/функции для компонента. И при каждом вызове метода в разметке/шаблоне Vue обращается к этому хуку.

У нас есть только два объявленных метода:

i. Send(). Этот метод вызывается, когда пользователь печатает сообщение и нажимает «Отправить». Он сохраняет сообщение в массиве message и отправляет события на сервер (см. выше).

ii. addUser(). Метод отправляет на сервер событие joined и устанавливает значение переменной ready как true, показывая, что пользователь успешно присоединился к чату.

addUser() {
this.ready = true;
socket.emit('joined', this.username)
}

2. Хук Created. Данный хук вызывается при загрузке компонента Vue.js. Это отличное место для прослушивания всех событий с сервера.

Мы прослушиваем 6 событий с сервера и отправляем 1 событие на сервер.

created() {
window.onbeforeunload = () => {
socket.emit('leave', this.username);
}

socket.on('chat-message', (data) => {
this.messages.push({
message: data.message,
type: 1,
user: data.user,
});
});

socket.on('typing', (data) => {
this.typing = data;
});


socket.on('stopTyping', () => {
this.typing = false;
});

socket.on('joined', (data) => {
this.info.push({
username: data,
type: 'joined'
});

setTimeout(() => {
this.info = [];
}, 5000);
});

socket.on('leave', (data) => {
this.info.push({
username: data,
type: 'left'
});

setTimeout(() => {
this.info = [];
}, 5000);
});

socket.on('connections', (data) => {
this.connections = data;
});
},

В коде выше видно, чем занимается каждое событие.

3. Хук Watch. Это очень интересная штука. Он может многое, но используется для прослушивания изменений в блоке сообщений и отправки события typing, которое транслируется сервером для других клиентов.

watch: {
newMessage(value) {
value ? socket.emit('typing', this.username) : socket.emit('stopTyping')
}
},

Еще он применяется для отметки о прочтении в разметках с используемыми директивами Vue.js. Вот полный файл index.html.

<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>ChatApp_Socket</title>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous" />
</head>

<body>
<div id="app">
<div class="container">
<div class="col-lg-6 offset-lg-3">

<div v-if="ready">
<p v-for="user in info">
{{user.username}} {{user.type}}
</p>
</div>

<div v-if="!ready">
<h4>Enter your username</h4>
<form @submit.prevent="addUser">
<div class="form-group row">
<input type="text" class="form-control col-9" v-model="username"
placeholder="Enter username here">
<input type="submit" value="Join" class="btn btn-sm btn-info ml-1">

</div>
</form>
</div>
<h2 v-else>{{username}}</h2>
<div class="card bg-info" v-if="ready">
<div class="card-header text-white">
<h4>My Chat App <span class="float-right">{{connections}} connections</span></h4>
</div>
<ul class="list-group list-group-flush text-right">
<small v-if="typing" class="text-white">{{typing}} is typing</small>
<li class="list-group-item" v-for="message in messages">
<span :class="{'float-left':message.type === 1}">
{{message.message}}
<small>:{{message.user}}</small>
</span>
</li>
</ul>

<div class="card-body">
<form @submit.prevent="send">
<div class="form-group">
<input type="text" class="form-control" v-model="newMessage"
placeholder="Enter message here">
</div>
</form>
</div>
</div>
</div>
</div>
</div>

</body>

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.js"></script>
<script src="/socket.io/socket.io.js"></script>

<script>
// Клиентская часть объекта Socket.IO
var socket = io();

// Создание нового экземпляра Vue
let vue = new Vue({

// Добавление корневого элемента в приложение Vue
el: '#app',

// Объявление объекта данных через пустые массивы и свойства
data: {
newMessage: null,
messages: [],
typing: false,
username: null,
ready: false,
info: [],
connections: 0,
},


// Создан метод Vue CREATED
created() {

// Отправление события 'leave' (покидает чат) во вкладку закрытых событий.
window.onbeforeunload = () => {
socket.emit('leave', this.username);
}

// Прослушивание события chat-message, отправленного с сервера и добавленного в массив message
socket.on('chat-message', (data) => {
this.messages.push({
message: data.message,
type: 1,
user: data.user,
});
});


// Прослушивание события typing, отправленного с сервера и показывающего данные (имя пользователя) в UI
socket.on('typing', (data) => {
this.typing = data;
});

// Прослушивание события stopTyping, отправленного с сервера и меняющего свойства typing на false
socket.on('stopTyping', () => {
this.typing = false;
});

// Прослушивание события joined, отправленного с сервера и добавляющего данные в массив info
socket.on('joined', (data) => {
this.info.push({
username: data,
type: 'joined'
});

// Установка времени ожидания до обнуления массива info
setTimeout(() => {
this.info = [];
}, 5000);
});

// Прослушивание события leave, отправляемого с сервера и добавляющего данные в массив info
socket.on('leave', (data) => {
this.info.push({
username: data,
type: 'left'
});

// Установка времени ожидания до обнуления массива info
setTimeout(() => {
this.info = [];
}, 5000);
});

// Прослушивание события connections, отправляемого с сервера. Показывает общее количество подключенных клиентов
socket.on('connections', (data) => {
this.connections = data;
});
},

// Vue-хук Watch
watch: {

// Просматривает изменения во входящих сообщениях и отправляет событие typing или stopTyping
newMessage(value) {
value ? socket.emit('typing', this.username) : socket.emit('stopTyping')
}
},

//Vue-хук Methods
methods: {

//Метод send сохраняет сообщение пользователя и отправляет событие на сервер.
send() {
this.messages.push({
message: this.newMessage,
type: 0,
user: 'Me',
});

socket.emit('chat-message', {
message: this.newMessage,
user: this.username
});
this.newMessage = null;
},

// Метод addUser отправляет событие joined с именем пользователя и устанавливает значение свойства ready как true.
addUser() {
this.ready = true;
socket.emit('joined', this.username)
}
},

});
</script>

</html>

Заключение.

Вы можете улучшить код, добавить авторизацию, группы, приватные беседы и управление базой данных, либо отслеживать все изменения через управление сеансами.

Код проекта на GitHub .

🙏 Спасибо за прочтение! 🙏

Надеюсь, эта статья была вам полезна. Если у вас есть вопросы или предложения, пожалуйста, оставляйте комментарии. Ваши отзывы помогают мне становиться лучше.

⚠️ Внимание! ⚠️

Если при чтении этой статьи вы обнаружите какие-либо ошибки в программном коде или грамматике, битые ссылки, пожалуйста, дайте мне знать. Заранее приношу извинения за возможные неудобства.