Вебсокеты на FastAPI: Реализация простого чата с комнатами за 20 минут

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

В ближайшее время я планирую опубликовать большую статью на Хабре, где подробно разберу разработку анонимного чата в формате Telegram MiniApp. Мы создадим сервис для общения тет-а-тет, который будет работать внутри Telegram и обеспечивать мгновенный обмен сообщениями.

Технологический стек проекта:

  • FastAPI + WebSocket – для реального времени и обмена сообщениями

  • Redis – для быстрого поиска и соединения собеседников

  • PostgreSQL – для хранения сообщений и информации о пользователях

  • Vue 3 + Pinia – для удобного и отзывчивого интерфейса

  • Telegram Mini Apps API – чтобы встроить чат прямо в Telegram

Кроме технического разбора, в статье я также затрону вопрос монетизации. Одним из самых эффективных способов монетизации Telegram MiniApp является реклама. Это позволяет оставить сервис бесплатным для пользователей, а разработчику — зарабатывать на популярности проекта.

В качестве рекламной платформы буду рассматривать RichAds – они предлагают отличные инструменты для интеграции рекламы в Telegram MiniApps, что делает их логичным выбором для такого проекта.

Но прежде чем погружаться в разработку MiniApp, давайте разберёмся с основами. В этой статье мы познакомимся с веб-сокетами, узнаем, как они работают, и научимся реализовывать их на FastAPI.

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

Итак, что такое WebSocket и как он поможет нам в разработке чата? Давайте разбираться.

WebSocket: принципы работы

Представьте себе телефонный звонок: вы набираете номер (устанавливаете соединение) один раз, и после этого можете свободно разговаривать, не набирая номер заново для каждой фразы. WebSocket работает похожим образом – это технология, которая создает постоянный "канал связи" между браузером пользователя и сервером, позволяя им обмениваться сообщениями в реальном времени.

Давайте разберемся с терминами:

  • Клиент – это то, чем пользуется человек: веб-страница в браузере, приложение на телефоне или компьютере

  • Сервер – это удаленный компьютер, который хранит и обрабатывает данные нашего приложения

Ключевые преимущества WebSocket:

  1. Постоянное соединение: как телефонный звонок – установили связь один раз и общаетесь

  2. Быстрая работа: не тратится время на повторное соединение

  3. Двусторонняя связь: и клиент, и сервер могут начать общение первыми

  4. Экономия ресурсов: служебная информация передается только в начале соединения

Сравнение подходов к реализации чата

Классический способ (HTTP):

Представьте, что вы отправляете письма:

  1. Пишете сообщение и отправляете его на сервер

  2. Сервер сохраняет письмо

  3. Другие участники чата должны постоянно проверять "почтовый ящик" (делать запросы к серверу)

  4. Только после проверки они увидят новое сообщение

Даже если проверка происходит автоматически (например, через AJAX), всё равно приходится постоянно "заглядывать в почтовый ящик", что создает лишнюю нагрузку.

Недостатки:

  • Сервер устает от постоянных проверок

  • Сообщения доходят с задержкой

  • Тратится много интернет-трафика

Способ с использованием WebSocket:

Теперь представьте групповой звонок:

  1. Все участники подключаются к общему разговору

  2. Когда кто-то говорит, все сразу это слышат

  3. Не нужно постоянно проверять, есть ли новые сообщения

  4. Общение происходит мгновенно

Преимущества:

  • Сообщения приходят моментально

  • Сервер меньше нагружен

  • Экономится интернет-трафик

  • Настоящее общение в реальном времени

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

Дальше мы создадим простой чат на FastAPI, чтобы увидеть, как это работает на практике. Для простоты примера мы сделаем и серверную, и клиентскую части в одном приложении, хотя в реальном чате "Тет-а-Тет" интерфейс будет отдельным приложением на VueJS3.

Какой проект мы будем разрабатывать?

Сегодня мы создадим полноценное FullStack-приложение – групповой чат, в котором пользователи смогут:

  • Создавать комнаты для общения

  • Подключаться к существующим комнатам

  • Обмениваться сообщениями в реальном времени

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

Технологический стек

Для реализации проекта мы будем использовать современные и удобные инструменты:

  • Python + FastAPI + WebSockets – для серверной части и организации реального времени

  • JavaScript – для установки соединений по веб-сокетам и динамики на странице

  • TailwindCSS – для быстрой и удобной стилизации интерфейса

  • HTML + Jinja2 – для рендеринга страниц с данными

  • Amvera Cloud – для быстрого и простого деплоя

Этапы разработки

1. Серверная часть (Backend)

  • Разработка класса для управления веб-сокетами

  • Создание эндпоинта для обработки подключений через WebSocket

  • Реализация маршрутов (эндпоинтов) для рендеринга HTML-страниц

2. Клиентская часть (Frontend)

  • Разработка двух HTML-страниц

  • Добавление JavaScript-логики для подключения к WebSocket

  • Реализация динамического обновления интерфейса

3. Деплой проекта

Групповой чат бессмыслен без доступа извне, поэтому в завершение мы развернём проект в интернете.

Для деплоя будем использовать Amvera Cloud – сервис, который позволяет развернуть FastAPI-приложение буквально за пару минут. Плюсы этого решения:

  • Автоматическое HTTPS-сертификаты

  • Бесплатное доменное имя

  • Поддержка вебхуков (например, для интеграции с Telegram-ботами)

Таким образом, по итогам работы у нас получится готовый, развернутый и рабочий групповой чат, который можно использовать, тестировать и расширять.

Подготовка проекта

Начнем с подготовки проекта. Напоминаю, что писать мы будем на Python фреймворке FastAPI, который отлично подходит для реализации WebSocket благодаря своей асинхронной природе и простому API.

  • Открываем IDE и создаем новый проект

  • Создаем файл requirements.txt и заполняем его следующим образом:

fastapi==0.115.8    # Сам веб-фреймворк
websockets==15.0    # Библиотека для работы с WebSocket
uvicorn==0.34.0     # ASGI сервер для запуска приложения
jinja2==3.1.5       # Шаблонизатор для рендеринга HTML
  • Устанавливаем библиотеки:

pip install -r requirements.txt
  • Подготовим структуру проекта:

my_chat_project/
├── requirements.txt         # Файл зависимостей проекта
├── app/                     # Главная директория приложения
│   ├── templates/           # Директория с HTML-шаблонами
│   │   ├── home.html        # Шаблон домашней страницы
│   │   └── index.html       # Основной шаблон приложения
│   ├── static/              # Директория со статическими файлами (JS, CSS)
│   │   └── index.js         # JavaScript для index.html
│   ├── api/                 # Директория с API-роутами
│   │   ├── router_page.py   # Роуты для страниц (HTML)
│   │   └── router_socket.py # Роуты для WebSocket-соединений
│   └── main.py              # Основной файл приложения (запуск)

Пишем серверную часть

Основную логику серверной части нашего приложения мы опишем в файле router_socket.py. Этот файл будет отвечать за управление WebSocket-соединениями и передачу сообщений между пользователями.

Импортируем необходимые модули

from fastapi import APIRouter, WebSocket, WebSocketDisconnect
from typing import Dict
  • FastAPI — используется для создания веб-приложения.

  • WebSocket, WebSocketDisconnect — классы для работы с WebSocket-соединениями. Первый позволяет устанавливать соединения, второй — обрабатывать их разрыв.

  • Dict — тип данных для удобной аннотации структуры хранения подключений пользователей.

Инициализация маршрутизатора

FastAPI использует маршрутизаторы (APIRouter) для организации эндпоинтов в логические блоки. В данном случае, создадим маршрутизатор с префиксом /ws/chat:

router = APIRouter(prefix="/ws/chat")

Этот маршрут будет обрабатывать все WebSocket-запросы, связанные с чатом.

Создание менеджера подключений

Для удобного управления соединениями создадим класс ConnectionManager. Он будет отвечать за подключение и отключение пользователей, а также за рассылку сообщений.

Полный код класса:

class ConnectionManager:
    def __init__(self):
        # Хранение активных соединений в виде {room_id: {user_id: WebSocket}}
        self.active_connections: Dict[int, Dict[int, WebSocket]] = {}

    async def connect(self, websocket: WebSocket, room_id: int, user_id: int):
        """
        Устанавливает соединение с пользователем.
        websocket.accept() — подтверждает подключение.
        """
        await websocket.accept()
        if room_id not in self.active_connections:
            self.active_connections[room_id] = {}
        self.active_connections[room_id][user_id] = websocket

    def disconnect(self, room_id: int, user_id: int):
        """
        Закрывает соединение и удаляет его из списка активных подключений.
        Если в комнате больше нет пользователей, удаляет комнату.
        """
        if room_id in self.active_connections and user_id in self.active_connections[room_id]:
            del self.active_connections[room_id][user_id]
            if not self.active_connections[room_id]:
                del self.active_connections[room_id]

    async def broadcast(self, message: str, room_id: int, sender_id: int):
        """
        Рассылает сообщение всем пользователям в комнате.
        """
        if room_id in self.active_connections:
            for user_id, connection in self.active_connections[room_id].items():
                message_with_class = {
                    "text": message,
                    "is_self": user_id == sender_id
                }
                await connection.send_json(message_with_class)

Разбор кода

  1. Конструктор класса

    • self.active_connections — словарь, который хранит активные соединения, сгруппированные по комнатам (room_id).

    • В каждой комнате (room_id) подключенные пользователи хранятся в виде {user_id: WebSocket}.

  2. connect

    • Принимает WebSocket-соединение, идентификатор комнаты (room_id) и пользователя (user_id).

    • Подтверждает соединение (websocket.accept()).

    • Добавляет WebSocket в self.active_connections.

  3. disconnect

    • Удаляет WebSocket пользователя из self.active_connections.

    • Если в комнате не осталось пользователей, удаляет комнату.

  4. broadcast

    • Отправляет сообщение всем пользователям в комнате.

    • Дополнительно добавляет флаг is_self, чтобы клиент мог визуально выделять свои сообщения.

Инициализация менеджера соединений

Создадим экземпляр класса ConnectionManager, который будем использовать в дальнейшем:

manager = ConnectionManager()

Создание WebSocket-эндпоинта

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

@router.websocket("/{room_id}/{user_id}")
async def websocket_endpoint(websocket: WebSocket, room_id: int, user_id: int, username: str):
    await manager.connect(websocket, room_id, user_id)
    await manager.broadcast(f"{username} (ID: {user_id}) присоединился к чату.", room_id, user_id)
    
    try:
        while True:
            data = await websocket.receive_text()
            await manager.broadcast(f"{username} (ID: {user_id}): {data}", room_id, user_id)
    except WebSocketDisconnect:
        manager.disconnect(room_id, user_id)
        await manager.broadcast(f"{username} (ID: {user_id}) покинул чат.", room_id, user_id)

Разбор кода

  1. Маршрут

    • Эндпоинт принимает три параметра из URL: room_id, user_id, username.

    • Каждый пользователь подключается по URL /ws/chat/{room_id}/{user_id}.

  2. Подключение

    • await manager.connect(...) — добавляет пользователя в список активных соединений.

    • await manager.broadcast(...) — уведомляет всех пользователей комнаты о новом участнике.

  3. Прием и передача сообщений

    • Бесконечный цикл (while True) слушает входящие сообщения через websocket.receive_text().

    • После получения сообщения оно рассылается всем пользователям комнаты через manager.broadcast(...).

  4. Отключение

    • Если соединение прерывается (WebSocketDisconnect), вызывается manager.disconnect(...).

    • Отправляется сообщение в чат о выходе пользователя.

В этом разделе мы создали серверную часть чата на WebSocket с использованием FastAPI. Мы:

  • Разобрались с импортами и маршрутизаторами.

  • Создали ConnectionManager для управления соединениями.

  • Написали WebSocket-эндпоинт для приема и рассылки сообщений.

В следующем разделе разберем, как подключить клиентскую часть и протестировать WebSocket-соединения.

Пишем клиентскую часть

Клиентская часть условно будет состоять из двух основных этапов:

  1. Описание эндпоинтов для рендеринга HTML-страниц

  2. Написание самой фронтенд-части: HTML + CSS + JS

В этом разделе мы опишем эндпоинты, которые будут обслуживать HTML-страницы. Реализуем их в файле app/router_page.py.

Импорты

Сначала импортируем необходимые модули:

from fastapi import APIRouter, Request, Form
from fastapi.templating import Jinja2Templates
from fastapi.responses import HTMLResponse
import random

Разберёмся, зачем нам каждый импорт:

  • APIRouter – позволяет организовать маршрутизацию в приложении.

  • Request – объект запроса, который передаётся в шаблон при рендеринге.

  • Form – используется для обработки данных, переданных через HTML-форму.

  • Jinja2Templates – нужен для работы с HTML-шаблонами.

  • HTMLResponse – указывает, что эндпоинт возвращает HTML-страницу.

  • Random - для генерации случайного ID пользователя

Инициализация роута и рендерера

Перед тем как описывать эндпоинты, создадим объект templates, который укажет, где хранятся HTML-шаблоны, и инициализируем роутер:

templates = Jinja2Templates(directory='app/templates')
router = APIRouter()

Описание эндпоинтов

Теперь создадим два эндпоинта. Первый будет отвечать за рендеринг главной страницы, а второй — за страницу комнаты (нашего группового чата).

Эндпоинт для главной страницы

@router.get("/", response_class=HTMLResponse)
async def home_page(request: Request):
    return templates.TemplateResponse("home.html", {"request": request})

Этот эндпоинт возвращает главную страницу home.html. Она будет содержать форму для входа в чат, которую разберём позже.

Эндпоинт для входа в чат

@router.post("/join_chat", response_class=HTMLResponse)
async def join_chat(request: Request, username: str = Form(...), room_id: int = Form(...)):
    # Простая генерация user_id
    user_id = random.randint(100, 100000)
    return templates.TemplateResponse("index.html",
                                      {"request": request,
                                       "room_id": room_id,
                                       "username": username,
                                       "user_id": user_id}
                                      )

Этот эндпоинт выполняет несколько задач:

  1. Получает username и room_id из формы (с помощью Form(...)).

  2. Генерирует случайный идентификатор пользователя в диапазоне от 100 до 100000

  3. Возвращает HTML-страницу index.html с переданными в шаблон параметрами:

    • room_id – ID комнаты, куда заходит пользователь.

    • username – имя пользователя.

    • user_id – сгенерированный идентификатор пользователя.

Зачем используется Form(...)?

В FastAPI существует несколько способов передавать данные в эндпоинт. В данном случае Form(...) указывает, что параметры username и room_id передаются через HTML-форму методом POST. Это удобно для обработки данных, введённых пользователем на веб-странице.

Если бы мы передавали данные в URL (как параметры запроса), нам пришлось бы использовать Query(...), а если бы передавали JSON – Body(...).

На этом этапе у нас готовы эндпоинты для работы с HTML-страницами. В следующем разделе мы реализуем фронтенд: создадим HTML-шаблоны, стили и подключим WebSocket.

Реализация фронтенда: HTML, CSS и JavaScript

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

  • HTML – для структуры страниц,

  • CSS (TailwindCSS) – для стилизации,

  • JavaScript – для обработки событий и работы с WebSocket.

1. Структура HTML

Мы создадим два основных HTML-файла:

  • home.html – главная страница с формой входа в чат.

  • index.html – страница чата, где будет происходить общение в реальном времени.

Главная страница (home.html)

<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Вход в чат</title>
    <script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-100 flex flex-col items-center justify-center min-h-screen p-4">
<h1 class="text-3xl font-bold mb-6">Добро пожаловать в чат</h1>

<form action="/join_chat" method="post" class="bg-white p-6 rounded-lg shadow-md w-full max-w-md">
    <label class="block text-gray-700">Введите ваше имя:</label>
    <input type="text" name="username" required
           class="w-full p-2 border border-gray-300 rounded-lg mt-2 focus:ring-2 focus:ring-blue-500">

    <label class="block text-gray-700 mt-4">Введите ID комнаты:</label>
    <input type="number" name="room_id" required min="1"
           class="w-full p-2 border border-gray-300 rounded-lg mt-2 focus:ring-2 focus:ring-blue-500">
    <button type="submit" class="w-full bg-blue-500 text-white px-4 py-2 rounded-lg mt-4 hover:bg-blue-600">Войти в
        чат
    </button>
</form>
</body>
</html>

Разбор кода

Этот шаблон делает следующее:

  1. Выводит заголовок с приветствием.

  2. Показывает форму входа, состоящую из двух полей:

    • Имя пользователя (username) – обычное текстовое поле.

    • ID комнаты (room_id) – поле для ввода числового идентификатора.

  3. После нажатия на кнопку "Войти в чат", данные отправляются методом POST на сервер (/join_chat).

Почему просто ввод ID комнаты?

В боевых проектах можно было бы сделать выбор комнаты из списка, но в нашем случае проще дать пользователю возможность самому ввести ID. Если комната существует – он подключится, если нет – создаст новую.

Страница чата (index.html)

<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Чат на WebSocket - Комната {{ room_id }}</title>
    <script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-100 flex flex-col items-center p-4">
<h1 class="text-2xl font-bold mb-4">Чат на WebSocket - Комната {{ room_id }}</h1>

<!-- Скрытый элемент для хранения данных комнаты -->
<div id="room-data"
     data-room-id="{{ room_id }}"
     data-username="{{ username }}"
     data-user-id="{{ user_id }}"
     class="hidden">
</div>

<!-- Область сообщений -->
<div id="messages"
     class="w-full max-w-lg h-96 overflow-y-auto border border-gray-300 bg-white p-4 rounded-lg shadow-md">
</div>

<!-- Поле ввода и кнопка -->
<div class="flex mt-4 w-full max-w-lg">
    <input id="messageInput"
           type="text"
           placeholder="Введите сообщение"
           class="flex-1 p-2 border border-gray-300 rounded-l-lg focus:outline-none focus:ring-2 focus:ring-blue-500"/>
    <button onclick="sendMessage()"
            class="bg-blue-500 text-white px-4 py-2 rounded-r-lg hover:bg-blue-600">Отправить
    </button>
</div>
<script src="/static/index.js"></script>
</body>
</html>

Разбор кода

Этот шаблон создаёт интерфейс чата:

  1. Выводит заголовок с номером комнаты ({{ room_id }}).

  2. Скрытый блок #room-data

    • Хранит данные, переданные сервером (room_id, username, user_id).

    • Сделан скрытым с помощью class="hidden".

    • В будущем JavaScript будет извлекать эти данные.

  3. Область сообщений (#messages) – здесь будут появляться сообщения пользователей.

  4. Поле ввода и кнопка "Отправить"

    • Поле (#messageInput) для ввода текста.

    • Кнопка onclick="sendMessage()" отправляет сообщение.

  5. Подключение index.js

    • В файле /static/index.js будет описана логика взаимодействия с WebSocket.

Зачем скрытый блок #room-data?

Передавать серверные переменные напрямую в JS-код не всегда удобно. С Jinja2 их можно вывести в HTML и затем прочитать их в JavaScript.

2. Подключение стилей

Мы используем TailwindCSS, который подключается через CDN:

<script src="https://cdn.tailwindcss.com"></script>

Это позволяет нам писать компактный и гибкий CSS-код прямо в HTML, например:

<body class="bg-gray-100 flex flex-col items-center p-4">

Почему TailwindCSS?

  • Избавляет от необходимости писать кастомные CSS-файлы.

  • Позволяет быстро стилизовать элементы.

  • Хорошо подходит для прототипирования.

3. Как страницы взаимодействуют с сервером

  1. Пользователь заходит на главную страницу сайта (/).

  2. Вводит имя и ID комнаты.

  3. Нажимает "Войти в чат" – запрос отправляется на /join_chat.

  4. Сервер возвращает index.html с переданными в него параметрами.

  5. Страница загружается, а JS-код начинает работу с WebSocket.

Что дальше?

Мы подготовили интерфейс, но пока чат не умеет отправлять сообщения. В следующем разделе реализуем JavaScript-логику, которая позволит подключаться к WebSocket, отправлять и получать сообщения.

Реализация WebSocket в JavaScript

Теперь, когда у нас есть интерфейс, пришло время добавить логику для обмена сообщениями в реальном времени. Мы будем использовать WebSocket для связи между клиентом и сервером.

Небольшое, но важное отступление

Хочу, чтобы у вас сложилось правильное понимание: реализация WebSocket, а особенно чатов, на стороне бэкенда — не такая уж сложная задача.

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

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

Почему?

  • Помимо визуальной составляющей интерфейса, им нужно правильно установить соединение с сервером.

  • Обрабатывать события WebSocket: подключение, отключение, получение и отправку сообщений.

  • Организовать удобное и динамическое отображение сообщений в режиме реального времени.

Конечно, эта работа не сверхсложная, но основные трудности при работе с WebSocket чаще возникают именно на фронтенде.

Далее я покажу на простом примере, как реализовать WebSocket-соединение на JavaScript и как с ним работать на клиентской стороне.

Дальнейший код описываем в файле app/static/index.js.

1. Инициализация WebSocket

Вначале необходимо получить данные о пользователе и комнате. Так как мы передавали их через Jinja2 в скрытом HTML-блоке (#room-data), извлечём их с помощью JavaScript:

// Получаем данные из скрытого элемента
const roomData = document.getElementById("room-data");
const roomId = roomData.getAttribute("data-room-id");
const username = roomData.getAttribute("data-username");
const userId = roomData.getAttribute("data-user-id");

Эти данные понадобятся нам для установки WebSocket-соединения.

2. Установка WebSocket-соединения

Создадим соединение с сервером:

const ws = new WebSocket(`ws://localhost:8000/ws/chat/${roomId}/${userId}?username=${username}`);

На этапе деплоя на сервис Amvera Cloud нам предстоит заменить эту ссылку на боевой https домен.

Что происходит здесь?

  • Мы создаём WebSocket-соединение по адресу ws://localhost:8000/ws/chat/.

  • В URL передаём ID комнаты (roomId), ID пользователя (userId) и имя пользователя (username).

  • Сервер использует эти данные, чтобы идентифицировать подключение.

Дополнительно, мы добавляем обработчики событий, чтобы отслеживать состояние соединения:

ws.onopen = () => {
    console.log("Соединение установлено");
};

ws.onclose = () => {
    console.log("Соединение закрыто");
};

3. Получение сообщений

Когда сервер отправляет сообщение, срабатывает обработчик onmessage. Разбираем JSON-данные и добавляем их в область чата:

ws.onmessage = (event) => {
    const messages = document.getElementById("messages");
    const messageData = JSON.parse(event.data);
    const message = document.createElement("div");

    // Определяем стили в зависимости от отправителя
    if (messageData.is_self) {
        message.className = "p-2 my-1 bg-blue-500 text-white rounded-md self-end max-w-xs ml-auto";
    } else {
        message.className = "p-2 my-1 bg-gray-200 text-black rounded-md self-start max-w-xs";
    }

    message.textContent = messageData.text;
    messages.appendChild(message);
    messages.scrollTop = messages.scrollHeight; // Автопрокрутка вниз
};

Разбор кода:

  • JSON.parse(event.data) – преобразует JSON-строку в объект.

  • Если is_self == true, значит сообщение отправил текущий пользователь, и оно отображается справа (синим цветом).

  • Иначе – сообщение от другого пользователя, и оно отображается слева (серым цветом).

  • Автопрокрутка (messages.scrollTop = messages.scrollHeight) – чтобы новые сообщения всегда были видны.

4. Отправка сообщений

Создадим функцию для отправки сообщений:

function sendMessage() {
    const input = document.getElementById("messageInput");
    if (input.value.trim()) {
        ws.send(input.value);
        input.value = '';
    }
}

Как это работает?

  1. Берём текст из поля ввода (#messageInput).

  2. Если сообщение не пустое, отправляем его через ws.send().

  3. Очищаем поле ввода.

Дополнительно, чтобы отправлять сообщения по Enter, добавляем обработчик события:

document.getElementById("messageInput").addEventListener("keypress", (e) => {
    if (e.key === "Enter") {
        sendMessage();
    }
});

Теперь пользователь может нажимать Enter, и сообщение будет отправляться автоматически.

5. Полный цикл работы чата

  1. Подключаемся к WebSocket-серверу при загрузке страницы.

  2. Ждём входящие сообщения и отображаем их в окне чата.

  3. При отправке сообщения пользователь вводит текст и нажимает кнопку или Enter.

  4. Сервер передаёт сообщение всем участникам комнаты.

  5. Чат обновляется в реальном времени без перезагрузки страницы.

Благодаря WebSocket и небольшому количеству JavaScript-кода у нас получился полноценный чат, работающий в режиме реального времени.

Теперь нам остается настроить main-файл (файл запуска) и можно начинать тестировать наше веб-приложение.

Финальные настройки и запуск приложения

На этом этапе нам осталось выполнить финальные настройки и запустить наше приложение.

Работать будем с файлом app/main.py, где реализуем три ключевые задачи:

  • Инициализируем FastAPI-приложение

  • Добавим обработку статических файлов (например, JS, CSS)

  • Зарегистрируем все маршруты (роуты)

Настраиваем main.py

Добавляем нужные импорты и описываем базовую конфигурацию:

from fastapi import FastAPI
from fastapi.staticfiles import StaticFiles
from app.api.router_page import router as router_page
from app.api.router_socket import router as router_socket

app = FastAPI()

# Подключаем папку со статическими файлами
app.mount('/static', StaticFiles(directory='app/static'), 'static')

# Регистрируем маршруты
app.include_router(router_socket)
app.include_router(router_page)

Так как наше веб-приложение само отвечает за рендеринг HTML-страниц, CORS-настройки нам не требуются.

Запуск приложения

Для старта используем uvicorn — это легковесный ASGI-сервер, который мы установили ранее.

Базовая команда:

uvicorn main:app

По умолчанию сервер поднимется на http://127.0.0.1:8000.

Если нужно настроить параметры запуска, добавляем флаги:

  • --host 0.0.0.0 – делает сервер доступным из внешней сети

  • --port 8005 – указывает конкретный порт

  • --reload – включает автоперезапуск при изменении кода (полезно в разработке)

Пример: Запуск на порту 8005 с автообновлением кода

uvicorn main:app --port 8005 --reload

После успешного запуска сервер готов к работе, и мы можем подключаться к чату!

Источник: https://habr.com/ru/companies/amvera/articles/884816/


Интересные статьи

Интересные статьи

Привет, Хабр! Сегодня поговорим о том, как расшифровывать звонки с клиентами через CRM-систему Битрикс24 вместо CoPilot. Для автоматизации подключим платформу МТС Exolve. Вы сможете получать все запис...
В начале января от действий скамера пострадала Mandiant — ИБ-дочка Google. Злоумышленник взломал аккаунт компании в Twitter* и использовал его для криптографического мошенничества. Оказалось, что в ин...
По оценкам Cybersecurity Ventures, к 2025 году общий объем данных в облаках достигнет 100 зеттабайт, или 50% всех мировых данных на тот момент. Ландшафт хранения этой информации разнообразен: от недор...
Евгений Шеленков — руководитель Университета Битрикс24 и тимлид команды «Коммуникации в реальном времени». В компании он с 2010 года — и до сих пор относится к работе как к любимому хобби
У некоторых бизнес-тренеров в области е-коммерса и консультантов по увеличению интернет-продаж на многие вопросы часто можно слышать универсальную отмазку — «надо тестировать» или другую (чтобы не...