Сделаем мини-чат:.
Ниже — разделы про что мы соберем, подготовка проекта и сервер на Node.js, чтобы быстро понять устройство материала, практические ограничения и типовые точки отказа.
- Что мы соберем
- Подготовка проекта
- Сервер на Node.js
- Клиент в браузере
- Запуск
- Как устроена рассылка
- Что будет, если отправить объект
- Частые ошибки
- Cannot find module 'ws'
- Страница открылась, но сообщения не ходят
- На HTTPS-сайте не работает ws://
- Сообщение приходит самому отправителю
- После перезапуска сервера клиенты отвалились
- Что добавить для реального проекта
- Ответы на эти вопросы могут быть для вас полезными
- Нужно ли ставить WebSocket-пакет в браузер?
- Почему используется один HTTP-сервер и для страницы, и для WebSocket?
- Можно ли сделать WebSocket на Express?
- Чем этот пример отличается от Socket.IO?
- Почему JSON нужно парсить?
- Что почитать дальше по WebSockets
Что мы соберем
Сделаем мини-чат:
- 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
Если вы собираете тему по шагам, рядом лучше открыть:
- WebSocket простыми словами: когда нужен real-time — вернуться к отличию WebSocket от HTTP и polling.
- WebSocket test online: как проверить соединение — проверить endpoint через браузер, Postman или online client.
- Nginx и WebSocket proxy: почему локально работает, а на сервере нет — подготовить мини-чат к запуску за Nginx и HTTPS.
- FastAPI WebSocket: уведомления в браузер — сравнить Node.js-подход с Python backend на FastAPI.



