ФронтендБэкендJavaScriptHTML / CSS

Как сделать чат на WebSocket на JavaScript: сообщения без перезагрузки

Создаём Node.js WebSocket-сервер и браузерный чат, открываем две вкладки, передаём JSON в реальном времени и обрабатываем подключение, ошибку и разрыв.

Кодик

Автор

7 мин чтения

WebSocket держит соединение открытым, поэтому сервер может отправить новое сообщение браузеру сразу, без очередного HTTP-запроса и перезагрузки. Мы поднимем сервер на Node.js, подключим две вкладки и увидим, как один JSON-пакет появляется у всех клиентов. Заодно добавим индикатор соединения, проверку входа и безопасный вывод текста.

Обычный fetch похож на вопрос и ответ: браузер обращается к серверу, получает данные и завершает запрос. Для чата нужно другое поведение. Пользователь не знает, когда собеседник напишет, поэтому серверу нужен открытый канал в обе стороны. WebSocket начинается с HTTP upgrade, затем соединение остаётся активным и передаёт сообщения небольшими фреймами.

1Подключаем

Каждая вкладка открывает ws://localhost:8080 и получает собственный socket.

2Рассылаем

Сервер проверяет JSON и отправляет нормализованное сообщение всем открытым клиентам.

3Показываем

Браузер создаёт li и записывает текст через textContent, не вставляя чужой HTML.

Чем WebSocket отличается от обычного fetch

Сервер запускает WebSocketServer на порту 8080. При connection он отправляет системное сообщение только новому клиенту. Когда приходит message, сервер ограничивает размер, разбирает JSON, проверяет name и text, добавляет время и рассылает объект всем sockets со статусом OPEN.

ЭтапЧто происходитПризнак результата
HandshakeБраузер просит upgrade до WebSocketСобытие open
SendКлиент сериализует имя и текст в JSONСервер получил bytes
BroadcastСервер валидирует и проходит по clientsКаждый OPEN socket получил пакет
RenderБраузер разбирает JSON и создаёт безопасный liДве вкладки видят сообщение

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

Путь WebSocket-сообщения
От входных данных до видимого результата. Открытый канал работает в обе стороны

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

Пишем сервер и браузерный клиент

Создайте каталог, выполните npm init -y и npm install ws. Сохраните server.mjs и запустите node server.mjs. HTML отдавайте отдельным локальным сервером, например npx serve .. Откройте адрес в двух вкладках. В браузере используется встроенный WebSocket; пакет ws импортируется только в server.mjs.

import WebSocket, { WebSocketServer } from "ws";

const wss = new WebSocketServer({ port: 8080 });

function broadcast(payload) {
  const json = JSON.stringify(payload);
  for (const client of wss.clients) {
    if (client.readyState === WebSocket.OPEN) client.send(json);
  }
}

wss.on("connection", (socket) => {
  socket.send(JSON.stringify({ type: "system", text: "Соединение установлено" }));

  socket.on("message", (raw) => {
    try {
      if (raw.length > 4_000) throw new Error("Слишком длинное сообщение");
      const input = JSON.parse(raw.toString());
      const name = String(input.name ?? "Гость").trim().slice(0, 30);
      const text = String(input.text ?? "").trim().slice(0, 500);
      if (!text) return;
      broadcast({ type: "chat", name: name || "Гость", text, at: Date.now() });
    } catch {
      socket.send(JSON.stringify({ type: "error", text: "Неверный формат сообщения" }));
    }
  });
});

console.log("WebSocket: ws://localhost:8080");
Что должно произойти после запуска
  • После загрузки клиент показывает статус «Онлайн» и системное сообщение.
  • Текст из первой вкладки сразу появляется в первой и второй.
  • Пустая строка не рассылается, а слишком большой или неверный JSON даёт ошибку.
  • После остановки server.mjs обе вкладки получают close и меняют статус на «Отключено».

Сервер не доверяет JSON только потому, что его отправил наш интерфейс. Любой клиент может подключиться вручную, поэтому длина и типы проверяются до broadcast.

Слепой broadcast и проверяемый чат
Что отличает устойчивый проект от случайного успеха. Каждый пакет проверяется до рассылки

Что происходит с одним сообщением

После события open обе стороны могут отправлять данные в любой момент. socket.send не является HTTP POST и не создаёт новый запрос. Сервер хранит набор активных wss.clients. Broadcast проходит по нему и отправляет пакет только соединениям WebSocket.OPEN. Порядок сообщений внутри одного соединения сохраняется, но глобальная история нигде не хранится.

ЧастьОтветственностьЧто проверить
Browser WebSocketОткрывает канал и отправляет строкиСостояние open перед send
ws serverПринимает соединения и message eventsПорт слушается один раз
JSON contractОписывает name, text, type и atНеверные данные отклонены
Broadcast loopРассылает пакет открытым clientsЗакрытый socket пропущен
DOM rendererСоздаёт безопасные элементы спискаИспользуется textContent

У соединения есть состояния CONNECTING, OPEN, CLOSING и CLOSED. Нельзя отправлять форму до OPEN, поэтому кнопка сначала disabled. Для нестабильной сети добавьте переподключение с растущей задержкой, но не запускайте бесконечный цикл каждую миллисекунду. Ping/pong помогает обнаруживать мёртвые соединения на сервере.

Не вставляйте сообщение через innerHTML. Текст приходит от другого клиента и считается недоверенным. Создавайте DOM-узлы и присваивайте textContent, чтобы строка не превратилась в выполняемый HTML.

Проверяем две вкладки и разрыв соединения

Откройте две вкладки с разными именами и отправьте по два сообщения. Затем введите строку Привет: она должна отображаться с угловыми скобками, а не жирным HTML. Остановите сервер и проверьте статус. Запустите снова: в базовом варианте вкладка сама не подключится, поэтому добавьте кнопку «Переподключиться» или функцию reconnect с ограниченным числом попыток.

Проверка своими руками
  1. Откройте чат в двух вкладках и подтвердите двустороннюю рассылку.
  2. Отправьте HTML-подобный текст и проверьте безопасный вывод.
  3. Заблокируйте кнопку до события open.
  4. Остановите сервер и покажите состояние close.
  5. Отправьте неверный JSON из консоли и получите error packet.
  6. Добавьте ручное переподключение без создания двух sockets одновременно.
Готово, если выполняются все пункты
  • Две вкладки видят одинаковые сообщения.
  • Неверный вход не роняет сервер.
  • Разрыв отображается в интерфейсе.
  • Чужой текст не выполняется как HTML.

Чат становится понятным, когда вы видите жизненный цикл соединения, а не только летящие сообщения. Open разрешает отправку, message приносит данные, close требует реакции, а сервер проверяет каждый пакет.

Четыре проверки realtime-соединения
Четыре проверки перед следующим шагом. Close и неверный JSON являются обычными сценариями

Готовим чат к реальному использованию

Для следующей версии добавьте комнаты и имя пользователя, но не доверяйте им из JSON. Сессию нужно проверять на сервере. Историю сообщений храните отдельно и выдавайте с пагинацией. Добавьте ping/pong, ограничение частоты и логирование технических событий без полного текста приватных сообщений.

Как изучить тему в Кодике

В Кодике сначала закрепите события, JSON и работу с DOM. Затем сделайте эхо через один WebSocket. Только после этого добавляйте broadcast и две вкладки. Такой порядок отделяет сеть от интерфейса.

ШагЧто изучить в КодикеМини-проверка
1События JavaScriptОбработать submit без перезагрузки
2JSON stringify и parseПередать name и text одной строкой
3DOM и textContentБезопасно вывести недоверенный текст
4Состояния WebSocketОбработать open, message и close
5Серверный broadcastРазослать пакет двум вкладкам

Не пытайтесь запомнить весь API. В тренажёре Кодика сначала воспроизведите чистую логику без библиотеки, затем восстановите подключение и обработку ошибок. Финальный тест: объяснить проект по памяти и добавить одну свою функцию.

Пакет ws подключают в браузер

В браузере уже есть WebSocket. Библиотека ws нужна Node.js серверу.

send вызывается до open

Проверяйте readyState и включайте форму после события open.

JSON считается безопасным

Клиент можно подменить. Проверяйте тип, длину и права на сервере.

Сообщение вставляется через innerHTML

Используйте textContent, иначе чужой текст может стать разметкой и скриптом.

Сверьтесь с первичным источником. Команды, версии и ограничения примера проверяйте по официальному MDN WebSocket API. Если интерфейс изменился, первичная документация важнее старого скриншота.

Что получится в итоге

Готовый результат: Как сделать чат на WebSocket на JavaScript: сообщения без перезагрузки
Две вкладки держат открытые соединения, сервер проверяет один JSON-пакет и мгновенно рассылает безопасный текст всем подключённым клиентам.

Короткие ответы
Почему браузеру не нужен пакет ws?

Класс WebSocket встроен в браузер. ws реализует клиент и сервер для Node.js, а здесь используется его серверная часть.

Сохраняет ли сервер историю автоматически?

Нет. Он знает только активные соединения и будущие пакеты. История требует отдельного хранилища.

Зачем проверять readyState?

Отправка допустима только для открытого соединения. Клиент может ещё подключаться или уже закрываться.

Как понять, что проект действительно работает?

Проверьте основной сценарий, неверный ввод, повторный запуск и один граничный случай. Затем объясните путь данных своими словами.

Можно ли начать с готового кода из статьи?

Да. Добейтесь результата, измените одно правило и соберите ключевой файл заново без копирования.

Разберите realtime на события, JSON и состояние соединения

События, DOM и JSON закрепите в курсе JavaScript. Сравните постоянный канал с обычным запросом в статье про Fetch API.

Ошибки отсутствующих элементов повторите по материалу про null, а интерфейс сообщения можно развить после Todo List на JavaScript.