PurpleSchool — курсы программирования онлайн
  • Бесплатно
    • Курсы
    • JavaScript Основы разработкиPython Основы PythonCSS CSS FlexboxКарта развития
    • База знанийИконка стрелки
    • Новостные рассылкиИконка стрелки
  • Пути
    • Frontend React разработчик
    • Frontend Vue разработчик
    • Backend разработчик Node.js
    • Fullstack разработчик React / Node.js
    • Mobile разработчик React Native
    • Backend разработчик Golang
    • Devops инженер
    • Backend разработчик Python
  • AI для кодаНовое
  • О нас
    • Отзывы
    • Реферальная программа
    • О компании
    • Контакты
  • Иконка открытия меню
    • Сообщество
    • PurpleПлюс
    • AI Собеседование
    • AI тренажёр
    • Проекты
PurpleSchool — платформа бесплатных roadmap и курсов для разработчиков
ютуб иконка
Telegram иконка
VK иконка
VK иконка
Курсы
ГлавнаяКаталог курсовFrontendBackendFullstack
Практика
КарьераПроектыPurpleПлюс
Материалы
БлогБаза знаний
Документы
Договор офертаПолитика конфиденциальностиПроверка сертификатаМиграция курсовРеферальная программа
Реквизиты
ИП Ларичев Антон АндреевичИНН 773373765379contact@purpleschool.ru

PurpleSchool © 2020 -2026 Все права защищены

  • Курсы
    • FrontendИконка стрелки
    • AI разработкаИконка стрелки
    • BackendИконка стрелки
    • DevOpsИконка стрелки
    • MobileИконка стрелки
    • ТестированиеИконка стрелки
    • Soft-skillsИконка стрелки
    • ДизайнИконка стрелки
    Иконка слояПерейти в каталог курсов
  • PurpleSchool — курсы программирования онлайн
    • AI для кодаНовое
    • Сообщество
    • PurpleПлюс
    • AI Собеседование
    • AI тренажёр
    • Проекты
    Главная
    Сообщество
    WebSocket на Node.js: пишем чат и real-time уведомления

    WebSocket на Node.js: пишем чат и real-time уведомления

    Аватар автора WebSocket на Node.js: пишем чат и real-time уведомления

    Антон Ларичев

    Иконка календаря09 июня 2026
    WebSocketNode.jsReal-timeBackendmiddleИконка уровня middle
    Картинка поста WebSocket на Node.js: пишем чат и real-time уведомления

    Введение

    WebSocket — это полнодуплексный протокол поверх TCP, который позволяет серверу и клиенту обмениваться сообщениями в реальном времени через одно постоянное соединение. В отличие от HTTP-запросов и long-polling, WebSocket экономит трафик и снижает задержку до миллисекунд, что делает его незаменимым для чатов, игровых лобби, биржевых тикеров и систем уведомлений.

    В статье разберём, как поднять WebSocket-сервер на Node.js с помощью библиотеки ws, написать минимальный чат с комнатами, добавить heartbeat-проверку живых соединений и реализовать broadcast-уведомления. Все примеры рассчитаны на Node.js 20+ и работают без дополнительных фреймворков.

    Как устроен WebSocket

    Клиент инициирует обычный HTTP-запрос с заголовком Upgrade: websocket. Если сервер согласен, он отвечает кодом 101 Switching Protocols, после чего TCP-соединение переключается в режим обмена бинарными фреймами. Дальше обе стороны могут отправлять данные в любой момент — никакого polling.

    Важные моменты, о которых часто забывают:

    • WebSocket не имеет встроенного авто-реконнекта — это задача клиента.
    • Прокси и балансировщики могут резать idle-соединения через 60 секунд.
    • Браузерный WebSocket не передаёт кастомные заголовки, поэтому токен авторизации обычно прокидывают через query-параметр или subprotocol.

    Базовый сервер на ws

    Установим библиотеку: npm install ws. Затем создадим простой сервер, который принимает соединения и отвечает эхо-сообщением.

    import { WebSocketServer } from 'ws';
    
    // Создаём WebSocket-сервер на порту 8080
    const wss = new WebSocketServer({ port: 8080 });
    
    wss.on('connection', (ws, req) => {
      // Логируем IP подключившегося клиента
      console.log('Новое подключение:', req.socket.remoteAddress);
    
      ws.on('message', (data) => {
        // data приходит как Buffer, приводим к строке
        const text = data.toString();
        console.log('Получено:', text);
        ws.send(`echo: ${text}`);
      });
    
      ws.on('close', () => {
        console.log('Клиент отключился');
      });
    });
    

    Это рабочий сервер, но он держит соединения вечно и не умеет рассылать сообщения нескольким клиентам. Перейдём к чату.

    Чат с комнатами

    Реализуем простой чат, в котором клиент при подключении указывает комнату через query-параметр, а сервер раздаёт сообщения только участникам этой комнаты.

    import { WebSocketServer } from 'ws';
    import { parse } from 'url';
    
    const wss = new WebSocketServer({ port: 8080 });
    
    // Храним соединения по комнатам: Map<roomId, Set<WebSocket>>
    const rooms = new Map();
    
    wss.on('connection', (ws, req) => {
      const { query } = parse(req.url, true);
      const room = query.room || 'general';
    
      // Регистрируем клиента в комнате
      if (!rooms.has(room)) rooms.set(room, new Set());
      rooms.get(room).add(ws);
      ws.room = room;
    
      ws.on('message', (raw) => {
        const message = raw.toString();
        // Рассылаем сообщение всем, кроме отправителя
        for (const client of rooms.get(room)) {
          if (client !== ws && client.readyState === ws.OPEN) {
            client.send(JSON.stringify({ room, message }));
          }
        }
      });
    
      ws.on('close', () => {
        // Убираем клиента и чистим пустую комнату
        rooms.get(room).delete(ws);
        if (rooms.get(room).size === 0) rooms.delete(room);
      });
    });
    

    Клиент подключается строкой new WebSocket('ws://localhost:8080?room=js-news'). Такой подход масштабируется на тысячи соединений в одном Node.js-процессе, а для горизонтального масштабирования между инстансами обычно подключают Redis Pub/Sub.

    Heartbeat и обнаружение мёртвых соединений

    TCP-соединение может «зависнуть» — клиент потерял сеть, но сервер ещё не знает об этом. Решается это ping/pong-фреймами на уровне протокола.

    function heartbeat() {
      // Помечаем соединение как живое при получении pong
      this.isAlive = true;
    }
    
    wss.on('connection', (ws) => {
      ws.isAlive = true;
      ws.on('pong', heartbeat);
    });
    
    // Каждые 30 секунд проверяем все соединения
    const interval = setInterval(() => {
      for (const ws of wss.clients) {
        if (ws.isAlive === false) {
          // Клиент не ответил на прошлый ping — закрываем
          ws.terminate();
          continue;
        }
        ws.isAlive = false;
        ws.ping();
      }
    }, 30000);
    
    wss.on('close', () => clearInterval(interval));
    

    Этот паттерн — стандарт де-факто из документации ws. Без него у вас постепенно накопятся «зомби»-соединения, и сервер начнёт жрать память.

    Real-time уведомления

    Для уведомлений нужен broadcast по конкретному пользователю, а не по комнате. Привяжем сокет к userId после авторизации и сделаем функцию отправки.

    const userSockets = new Map(); // userId -> Set<WebSocket>
    
    wss.on('connection', (ws, req) => {
      const { query } = parse(req.url, true);
      // Проверяем JWT-токен, выдаём userId
      const userId = verifyToken(query.token);
      if (!userId) return ws.close(4001, 'Unauthorized');
    
      if (!userSockets.has(userId)) userSockets.set(userId, new Set());
      userSockets.get(userId).add(ws);
    
      ws.on('close', () => userSockets.get(userId).delete(ws));
    });
    
    // Вызываем из любого места приложения
    export function notify(userId, payload) {
      const sockets = userSockets.get(userId);
      if (!sockets) return;
      const data = JSON.stringify(payload);
      for (const ws of sockets) {
        if (ws.readyState === ws.OPEN) ws.send(data);
      }
    }
    

    Теперь любой сервис (создание заказа, новое сообщение, изменение статуса) может позвать notify(userId, {...}) и пользователь моментально увидит событие в UI.

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

    • Передача токена в заголовке. Браузер не умеет добавлять Authorization к WebSocket-запросу. Используйте query-параметр или subprotocol и сразу закрывайте соединение при невалидном токене.
    • Отсутствие heartbeat. Без ping/pong зависшие клиенты копятся часами и съедают память.
    • Broadcast без проверки readyState. Если отправить в закрывающийся сокет, получите исключение и уроните весь цикл рассылки.
    • Хранение state только в памяти процесса. При перезапуске или нескольких инстансах за балансировщиком сообщения теряются. Решение — Redis Pub/Sub или отдельный сервис состояния.
    • Игнорирование размера сообщения. По умолчанию ws принимает фреймы до 100 МБ. Установите лимит через опцию maxPayload, иначе один клиент уронит сервер.

    Заключение

    WebSocket на Node.js закрывает почти весь спектр real-time задач: от чатов до биржевых лент. Библиотека ws даёт минималистичный, но достаточный API, а связка с heartbeat и Redis Pub/Sub позволяет дорасти до продакшена с десятками тысяч одновременных соединений. Начните с простого эхо-сервера, добавьте комнаты, прикрутите авторизацию и мониторинг живых соединений — и у вас будет полноценная real-time платформа без тяжёлых фреймворков.

    Иконка глаза28

    Комментарии

    0

    Постройте личный план изучения Vue.js 3, Vue Router и Pinia до уровня Middle — бесплатно!

    Vue.js 3, Vue Router и Pinia — часть карты развития Frontend

    • step100+ шагов развития
    • lessons30 бесплатных лекций
    • lessons300 бонусных рублей на счет

    Бесплатные лекции

    Лучшие курсы по теме

    изображение курса

    Angular

    Антон Ларичев
    AI-тренажерыAI-тренажеры
    Гарантия
    Бонусы
    иконка звёздочки рейтинга5.0
    3 999 ₽ 6 990 ₽
    Подробнее
    изображение курса

    Nuxt

    Антон Ларичев
    AI-тренажерыAI-тренажеры
    Практика в студииПрактика в студии
    Гарантия
    Бонусы
    иконка звёздочки рейтинга5.0
    3 999 ₽ 6 990 ₽
    Подробнее
    изображение курса

    Feature-Sliced Design

    Антон Ларичев
    AI-тренажерыAI-тренажеры
    Практика в студииПрактика в студии
    Гарантия
    Бонусы
    иконка звёздочки рейтинга4.5
    3 999 ₽ 6 990 ₽
    Подробнее

    Похожие статьи

    Картинка поста WebSocket на Node.js: строим real-time чат с нуля за час
    Иконка аватараАнтон
    Иконка календаря26 мая 2026
    Node.jsWebSocketJavaScript+ 2middleИконка уровня middle

    WebSocket на Node.js: строим real-time чат с нуля за час

    WebSocket на Node.js: пошаговое руководство по созданию real-time чата с библиотекой ws, обработкой подключений и broadcast-сообщений.

    Иконка чипа0
    Иконка глаза257
    Иконка комментариев0
    Картинка поста Архитектура Node.js: слои, модули и Dependency Injection
    Иконка аватараАнтон
    Иконка календаря08 июня 2026
    Node.jsАрхитектураBackend+ 1middleИконка уровня middle

    Архитектура Node.js: слои, модули и Dependency Injection

    Архитектура Node.js приложения: разбираем слоистую структуру, разделение на модули и применение Dependency Injection для масштабируемого кода.

    Иконка чипа0
    Иконка глаза76
    Иконка комментариев0
    Картинка поста REST API на Node.js и Express с нуля: пошаговое руководство
    Иконка аватараАнтон
    Иконка календаря02 июня 2026
    Node.jsExpressREST API+ 1juniorИконка уровня junior

    REST API на Node.js и Express с нуля: пошаговое руководство

    REST API на Node.js и Express: пошаговое руководство для начинающих. Создаём сервер, маршруты, middleware и обрабатываем запросы.

    Иконка чипа0
    Иконка глаза180
    Иконка комментариев0
    Иконка чипа0