WebSocket на JavaScript и Node.js: мини-чат в браузере

Сделаем мини-чат:.

Ниже — разделы про что мы соберем, подготовка проекта и сервер на Node.js, чтобы быстро понять устройство материала, практические ограничения и типовые точки отказа.


Что мы соберем

Сделаем мини-чат:

  • Node.js-сервер принимает WebSocket-соединения;
  • браузер открывает HTML-страницу;
  • пользователь вводит сообщение;
  • сервер рассылает сообщение всем подключенным клиентам;
  • проверяем результат в двух вкладках браузера.

Это не мессенджер и не продакшен-чат. Это учебный минимальный проект, который показывает главный принцип WebSocket: открытое соединение и двусторонние сообщения

Подготовка проекта

Нужен установленный Node.js

Создайте папку:

mkdir websocket-mini-chat
cd websocket-mini-chat
npm init -y
npm install ws

Пакет ws нужен для WebSocket-сервера в Node.js. В браузере отдельный пакет не нужен: там уже есть встроенный объект WebSocket

Структура:

websocket-mini-chat/
  package.json
  server.js
  public/
    index.html

Создайте папку:

mkdir public

Сервер на Node.js

Создайте server.js:

const http = require('http');
const fs = require('fs');
const path = require('path');
const { WebSocketServer, WebSocket } = require('ws');

const port = Number(process.env.PORT || 3000);

const server = http.createServer((req, res) => {
  if (req.url === '/' || req.url === '/index.html') {
    const filePath = path.join(__dirname, 'public', 'index.html');
    const html = fs.readFileSync(filePath);

    res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
    res.end(html);
    return;
  }

  res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
  res.end('Not found');
});

const wss = new WebSocketServer({ server });

wss.on('connection', (socket) => {
  console.log('Клиент подключился');

  socket.send(JSON.stringify({
    type: 'system',
    text: 'Вы подключились к чату'
  }));

  socket.on('message', (buffer) => {
    const text = buffer.toString().trim();

    if (!text) {
      return;
    }

    const message = JSON.stringify({
      type: 'message',
      text,
      time: new Date().toLocaleTimeString('ru-RU')
    });

    for (const client of wss.clients) {
      if (client.readyState === WebSocket.OPEN) {
        client.send(message);
      }
    }
  });

  socket.on('close', () => {
    console.log('Клиент отключился');
  });

  socket.on('error', console.error);
});

server.listen(port, () => {
  console.log(`Открой http://localhost:${port}`);
});

Что важно:

  • http.createServer отдает HTML-страницу;
  • WebSocketServer({ server }) подключает WebSocket к тому же серверу;
  • событие connection срабатывает для нового клиента;
  • событие message принимает сообщение;
  • wss.clients хранит подключенных клиентов;
  • перед отправкой проверяем client.readyState === WebSocket.OPEN.

Клиент в браузере

Создайте public/index.html:

<!doctype html>
<html lang="ru">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>WebSocket мини-чат</title>
    <style>
      body {
        font-family: system-ui, sans-serif;
        max-width: 720px;
        margin: 40px auto;
        padding: 0 16px;
      }

      #messages {
        min-height: 240px;
        border: 1px solid #ddd;
        padding: 12px;
        margin-bottom: 12px;
      }

      form {
        display: flex;
        gap: 8px;
      }

      input {
        flex: 1;
        padding: 10px;
      }

      button {
        padding: 10px 14px;
      }
    </style>
  </head>
  <body>
    <h1>WebSocket мини-чат</h1>
    <div id="status">Подключаемся...</div>
    <div id="messages"></div>

    <form id="form">
      <input id="input" autocomplete="off" placeholder="Введите сообщение">
      <button>Отправить</button>
    </form>

    <script>
      const statusEl = document.querySelector('#status');
      const messagesEl = document.querySelector('#messages');
      const form = document.querySelector('#form');
      const input = document.querySelector('#input');

      const protocol = location.protocol === 'https:' ? 'wss' : 'ws';
      const socket = new WebSocket(`${protocol}://${location.host}`);

      function addMessage(text) {
        const div = document.createElement('div');
        div.textContent = text;
        messagesEl.append(div);
      }

      socket.addEventListener('open', () => {
        statusEl.textContent = 'Соединение открыто';
      });

      socket.addEventListener('message', (event) => {
        const data = JSON.parse(event.data);

        if (data.type === 'system') {
          addMessage(`[system] ${data.text}`);
          return;
        }

        addMessage(`[${data.time}] ${data.text}`);
      });

      socket.addEventListener('close', () => {
        statusEl.textContent = 'Соединение закрыто';
      });

      socket.addEventListener('error', () => {
        statusEl.textContent = 'Ошибка соединения';
      });

      form.addEventListener('submit', (event) => {
        event.preventDefault();

        const text = input.value.trim();

        if (!text || socket.readyState !== WebSocket.OPEN) {
          return;
        }

        socket.send(text);
        input.value = '';
      });
    </script>
  </body>
</html>

Здесь браузер сам выбирает ws или wss в зависимости от протокола страницы. Локально будет ws://localhost:3000, на HTTPS-сайте — wss://...

Запуск

Запустите:

node server.js

Откройте:

http://localhost:3000

Теперь откройте эту же страницу во второй вкладке. Напишите сообщение в первой вкладке. Оно должно появиться в обеих

Вот это и есть момент, ради которого WebSocket вообще стоит изучать: сервер рассылает событие всем подключенным клиентам сразу

Как устроена рассылка

Ключевой кусок:

for (const client of wss.clients) {
  if (client.readyState === WebSocket.OPEN) {
    client.send(message);
  }
}

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

Что будет, если отправить объект

WebSocket отправляет строки, бинарные данные или буферы. Поэтому объект обычно превращают в JSON:

socket.send(JSON.stringify({
  type: 'message',
  text: 'Привет'
}));

А на другой стороне читают:

const data = JSON.parse(event.data);

Так можно передавать не только текст, но и тип события:

  • message;
  • typing;
  • user_joined;
  • user_left;
  • notification.

Частые ошибки

Cannot find module 'ws'

Вы не установили пакет:

npm install ws

или запускаете сервер не из папки проекта

Страница открылась, но сообщения не ходят

Откройте DevTools и посмотрите Console/Network. Возможно, WebSocket не подключился или страница пытается подключиться к неправильному адресу

На HTTPS-сайте не работает ws://

Нужен wss://. Иначе браузер может заблокировать соединение

Сообщение приходит самому отправителю

В нашем учебном чате это нормально. Если хотите отправлять всем, кроме автора, добавьте проверку:

if (client !== socket && client.readyState === WebSocket.OPEN) {
  client.send(message);
}

После перезапуска сервера клиенты отвалились

Это нормально. В реальном приложении клиент должен уметь переподключаться

Что добавить для реального проекта

Мини-чат показывает принцип, но для продакшена нужны:

  • авторизация пользователя;
  • комнаты или каналы;
  • проверка формата сообщений;
  • ограничение размера сообщения;
  • защита от спама;
  • хранение истории в базе;
  • reconnect на клиенте;
  • heartbeat/ping;
  • нормальный proxy для wss://;
  • логирование ошибок.

Ответы на эти вопросы могут быть для вас полезными

Нужно ли ставить WebSocket-пакет в браузер?

Нет. В браузере есть встроенный WebSocket. Пакет ws мы ставим для сервера на Node.js

Почему используется один HTTP-сервер и для страницы, и для WebSocket?

Так удобнее для учебного примера: HTML и WebSocket живут на одном localhost:3000. В реальном проекте можно разделять frontend и backend

Можно ли сделать WebSocket на Express?

Express сам по себе работает с HTTP-маршрутами. WebSocket обычно подключают через отдельную библиотеку и HTTP-сервер, на котором висит Express

Чем этот пример отличается от Socket.IO?

Socket.IO — библиотека со своим протоколом поверх WebSocket и fallback-механизмами. Здесь мы используем обычный WebSocket через пакет ws

Почему JSON нужно парсить?

Потому что по WebSocket часто передают строку. Чтобы передать структуру с type, text, time, мы сериализуем объект в JSON и разбираем его на другой стороне

Что почитать дальше по WebSockets

Если вы собираете тему по шагам, рядом лучше открыть:

Оцените статью
0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
0
Оставьте комментарий! Напишите, что думаете по поводу статьи.x