Backend
Node.js и Express 5. REST API для CRUD-операций, WebSocket для событий в реальном времени. Один процесс, один порт.
b8q построен на проверенных технологиях с упором на производительность, простой деплой и предсказуемое поведение. Каждая зависимость решает конкретную задачу, модных фреймворков ради моды здесь нет.
Четыре слоя, каждый из которых можно понять, отладить и заменить независимо от остальных.
Node.js и Express 5. REST API для CRUD-операций, WebSocket для событий в реальном времени. Один процесс, один порт.
SPA на Vanilla JS: 32 модуля, ни одного фреймворка. Темы на CSS-переменных, работает без сборки.
WebSocket для сообщений и событий, Yjs для CRDT-синхронизации документов. Конфликтов при совместном редактировании нет.
SQLite в режиме WAL. Один файл внутри процесса, чтение без сетевой задержки. Бэкап — копия файла.
Сервер b8q — один процесс Node.js на Express 5. Пятая версия выбрана из-за нативной поддержки async/await: ошибки в асинхронных обработчиках ловит middleware, обёртки вроде express-async-errors не нужны, и уходит целый класс багов с unhandled promise rejection.
API устроен по REST: у каждого ресурса (пользователи, чаты, сообщения, документы, файлы, доски) стандартные CRUD-эндпоинты. На том же порту работает WebSocket-сервер: при загрузке клиент получает данные через REST и подписывается на обновления по WebSocket.
Сервер запускается одной командой node server.js, без Docker и внешних сервисов, кроме необязательного TURN-сервера для звонков. Установка сводится к трём действиям: скопировать файлы, поставить зависимости через npm, запустить. Для production рекомендуем PM2 или systemd, но и без них сервер работает стабильно.
Цепочка middleware минимальна: CORS, разбор JSON, проверка JWT, rate limiter. Логи пишутся через console с отметкой времени, в production они перенаправляются в файл или syslog.
Фронтенд — SPA на чистом JavaScript, без React, Vue и Angular. Каждый из 32 модулей отвечает за свою часть интерфейса (ChatModule, CallModule, DocumentEditor, KanbanBoard, FileManager и другие), загружается как ES-модуль и регистрируется в центральном роутере.
Без фреймворка весь JavaScript приложения занимает десятки килобайт, а не мегабайты, и страница загружается мгновенно. Виртуального DOM и реактивной системы нет: с DOM работаем напрямую, через querySelector и createElement. Для 90% задач этого достаточно.
Стили построены на CSS-переменных: шесть тем переключаются сменой набора переменных на :root, а при выборе своего акцентного цвета вся схема пересчитывается автоматически. Никакого CSS-in-JS и Tailwind, только обычные CSS-файлы с понятной структурой.
Сборка не нужна: код работает в браузере напрямую через <script type="module">. Минификация через terser необязательна, отладка идёт в DevTools без source maps и webpack.
В b8q два канала реального времени. Первый — WebSocket для системных событий: новые сообщения, индикатор набора, статусы прочтения, изменения на канбан-досках, уведомления о файлах. Соединение открывается при входе и живёт всю сессию, при разрыве клиент переподключается с экспоненциальной задержкой.
Второй канал — Yjs для совместного редактирования документов. Это CRDT-библиотека (Conflict-free Replicated Data Type): каждая операция коммутативна и идемпотентна, поэтому одновременные правки сливаются без конфликтов, а правила «побеждает последний сохранивший» нет.
Yjs синхронизируется поверх WebSocket: открывая документ, клиент запрашивает текущее состояние и подписывается на обновления. Каждое нажатие клавиши уходит как операция, и результат одинаков на всех устройствах, даже если двое печатали в одном месте.
Производительность Yjs масштабируется линейно по числу операций: документ с тысячами правок открывается за десятки миллисекунд. История изменений хранится в компактном бинарном виде.
Звонки построены на WebRTC, стандарте W3C для P2P-аудио и видео в браузере. Медиапотоки идут напрямую между участниками, а через сервер по WebSocket проходит только сигнализация (offer, answer, ICE-кандидаты). Отсюда минимальная задержка и отсутствие нагрузки на сервер.
Соединение устанавливается через ICE: сначала клиенты пробуют прямой путь через STUN, а при симметричном NAT или корпоративном файрволе трафик идёт через TURN-relay. Свои STUN- и TURN-серверы задаются в конфигурации.
Качество видео адаптивное: клиент следит за пропускной способностью через RTCPeerConnection.getStats() и переключает разрешение — 480p на слабом канале, 720p на обычном, 1080p на хорошем. Звонок при этом не прерывается.
Групповые звонки на 2–5 участников идут по mesh-топологии: каждый соединён с каждым, для пяти человек это 10 соединений. Медиасервер (SFU/MCU) не нужен, это упрощает деплой, но ограничивает масштаб. Для большинства рабочих созвонов пяти участников хватает.
Для self-hosted мессенджера на одном сервере SQLite удобнее PostgreSQL или MySQL: нет отдельного процесса БД, сетевых вызовов и настройки пулов соединений. SQLite встроен в процесс Node.js через better-sqlite3, и запрос к базе — это вызов функции, а не сетевой roundtrip.
Ключевая настройка — WAL (Write-Ahead Logging): чтение и запись не блокируют друг друга. Сотни запросов к истории чатов выполняются параллельно и не мешают записи новых сообщений.
На типичной нагрузке мессенджера SQLite в режиме WAL выдерживает десятки тысяч операций в секунду, этого с запасом хватает команде до нескольких сотен человек. База — один файл: бэкап делается командой cp database.db database.db.backup, миграции — SQL-файлы, которые выполняются при запуске сервера.
Схема нормализована (users, chats, messages, documents, files, boards, cards), на частых полях chat_id, created_at и user_id стоят индексы. Полнотекстовый поиск по сообщениям работает на FTS5, который входит в стандартную сборку SQLite.
AI-ассистент подключается к Google Gemini через REST API. Сервер собирает промпт с контекстом (история диалога и системная инструкция), отправляет запрос в Gemini и возвращает ответ пользователю обычным сообщением в чате.
Если Gemini недоступен (таймаут, исчерпана квота, сбой сети), включается резервный режим на правилах: заготовленные ответы на типовые запросы, помощь по интерфейсу, базовые команды и FAQ. Полноценную LLM он не заменяет, но ассистент не замолкает.
Промпт собирается на сервере, клиент отправляет только текст пользователя. Поэтому поведение модели настраивается централизованно: администратор меняет системный промпт без обновления клиента.
Если требования к данным жёсткие, AI-модуль полностью отключается в конфигурации сервера, и ни один запрос не уходит во внешний сервис. Это важно для работы с персональными данными, финансовой информацией и коммерческой тайной.
Авторизация построена на JWT (JSON Web Tokens). При входе сервер проверяет пароль, формирует токен с user_id и ролью и подписывает его секретным ключом, а клиент передаёт токен в заголовке Authorization в каждом запросе. Срок жизни токена задаётся в конфигурации.
Пароли хранятся как bcrypt-хеши. Алгоритм намеренно медленный (cost factor настраивается), соль встроена в хеш, поэтому rainbow tables бесполезны. Даже при утечке базы восстановить пароли при адекватном cost factor практически невозможно.
Rate limiting защищает вход от перебора паролей, а API от DoS. Лимиты задаются для каждого эндпоинта: на вход жёстче, на чтение сообщений мягче, при превышении сервер отвечает 429 Too Many Requests. В self-hosted пороги настраивает администратор.
WebSocket-соединение проверяется при установке: клиент передаёт JWT, сервер проверяет подпись, и неавторизованные соединения отклоняются до передачи данных. В production трафик идёт по HTTPS/WSS, TLS обеспечивает reverse proxy (nginx, Caddy).
Ключевые этапы b8q от первого прототипа до полноценной платформы.
Архитектуру лучше всего оценивать в работе: от git clone до первого сообщения 5 минут.