Командный чат «Планёрки»: сообщения, инкрементальный поллинг, комнаты и защита от спама. В финале — настоящий WebSocket-сервер на ws.
Команда StudyCode устала от общих мессенджеров: рабочие обсуждения тонут между мемами, рекламой и семейными чатами. Решение принято — пишем свой командный чат «Планёрки». Маленький, быстрый, только для своих.
Фронтендер уже рисует окно чата. С тебя — бекенд.
HTTP устроен как разговор по рации: клиент спрашивает — сервер отвечает. Сам сервер «постучаться» к браузеру не может: нет запроса — нет ответа. А в чате сообщение от Ани должно появиться у Бориса без нажатия F5. Как быть?
За годы придумали четыре подхода:
| Подход | Как работает | Задержка | Сложность | Когда выбирать |
|---|---|---|---|---|
| Короткий поллинг | клиент сам спрашивает «что нового?» каждые N секунд | до N сек | ⭐ | прототипы, редкие обновления (курс валют, статус заказа) |
| Long-polling | сервер держит запрос открытым, пока не появятся новости | почти мгновенно | ⭐⭐ | чаты там, где WebSocket недоступен |
| SSE (Server-Sent Events) | одно соединение, сервер шлёт поток событий — только сервер → клиент | почти мгновенно | ⭐⭐ | ленты, уведомления, прогресс-бары |
| WebSocket | постоянное двустороннее соединение поверх TCP | почти мгновенно | ⭐⭐⭐ | чаты, игры, совместные редакторы |
Начинать принято с простого: короткий поллинг — это обычные GET-запросы по таймеру. Никакой магии, зато работает везде и пишется за вечер. Именно так делают первый прототип чата.
Наша песочница умеет только «запрос → ответ», поэтому в ней мы построим чат на поллинге — и он будет по-настоящему работать (превью справа будет само опрашивать твой сервер по таймеру!).
А настоящий WebSocket-сервер ты напишешь в финальном шаге на своём компьютере — на пакете ws, с рассылкой сообщений всем подключённым и обработкой отключений. Там и почувствуешь разницу.
Вот что построим за проект:
| Метод | Путь | Что делает | Статусы |
|---|---|---|---|
GET | /api/messages?after=N | Сообщения (новее id N) | 200 |
POST | /api/messages | Отправить сообщение | 201 / 400 |
GET | /api/rooms | Список комнат с числом сообщений | 200 |
GET | /api/rooms/:room/messages?after=N | Сообщения комнаты | 200 / 404 |
POST | /api/rooms/:room/messages | Сообщение в комнату | 201 / 400 / 404 / 429 |
Параметр ?after=N — сердце поллинга: клиент запоминает id последнего сообщения и просит только новое. А 429 — новый для нас статус: защита от спамеров, доберёмся до неё на шестом шаге 😈
Хранилище — обычный массив в памяти плюс счётчик id:
let messages = [
{ id: 1, user: 'Аня', text: 'Всем привет!' },
{ id: 2, user: 'Борис', text: 'Погнали делать чат' },
];
let nextId = 3;Справа — скелет первой половины проекта, чтобы видеть картину целиком. Запоминать не нужно: каждый маршрут соберёшь сам и сразу проверишь тестами. Погнали! 🚀
const app = express();
app.use(express.json());
let messages = [
{ id: 1, user: 'Аня', text: 'Всем привет!' },
{ id: 2, user: 'Борис', text: 'Погнали делать чат' },
];
let nextId = 3;
// GET — сообщения (с поддержкой ?after=N для поллинга)
app.get('/api/messages', (req, res) => {
const after = Number(req.query.after) || 0;
res.json(messages.filter(m => m.id > after));
});
// POST — отправить сообщение
app.post('/api/messages', (req, res) => {
const user = req.body.user;
const text = req.body.text;
if (!user || !text || !text.trim()) {
return res.status(400).json({ error: 'Нужны user и text' });
}
if (text.length > 500) {
return res.status(400).json({ error: 'Слишком длинное сообщение (максимум 500)' });
}
const message = { id: nextId++, user: user, text: text.trim() };
messages.push(message);
res.status(201).json(message);
});
// Дальше в проекте: комнаты (/api/rooms/...) и защита от спама (429)
app.listen(3000, () => console.log('✅ http://localhost:3000'));Первое, что делает фронт при открытии — просит список сообщений. Каждое сообщение { id, user, text }, где id — растущий номер: именно по нему потом отличают новое от старого.