A real-time room-based chat application built with FastAPI, WebSockets, and Redis. Supports multi-server deployments with room-scoped messaging and presence tracking.
- User Authentication: JWT-based registration and login with bcrypt password hashing
- Room Management: Create public/private rooms with membership controls
- Real-time Messaging: WebSocket-based room-scoped message delivery
- Multi-server Support: Redis pub/sub for cross-server message routing by room ID
- Global Presence: Track which users are online globally
- Room Presence: Track which users are online within each room
- REST API: Full CRUD for rooms, membership, and presence queries
- Soft-delete Membership: Preserve membership history with
left_attimestamps
- Private rooms require explicit membership
- Room owners control who can leave/access rooms
- Presence endpoints enforce membership visibility for private rooms
WebSocket events use a standardized JSON envelope:
{
"event": "room.message|room.join|room.leave|room.presence_update",
"room_id": 1,
"sender": {"user_id": 1, "username": "alice"},
"payload": {"message": "hello"},
"timestamp": "2026-04-28T06:34:56.489598+00:00"
}FastAPI WebSocket Endpoint → Local Room Connections Map → Client Sockets
Server A (joins room 5) → Redis pub/sub (channel="broadcast")
↓
Server B (listening) → Receives room_id=5 → Routes to local room subscribers only
Room subscription tracking keeps each server aware of which sockets are subscribed to which rooms. Redis pub/sub includes the room_id in the payload, so servers can route messages to the correct local audience without broadcasting globally.
- Framework: FastAPI 0.135.3 (async)
- Database: SQLAlchemy 2.0.49 ORM with SQLite
- WebSocket: FastAPI native with lifespan async context management
- Authentication: JWT (python-jose), bcrypt password hashing
- Real-time: Redis 7.4.0 async client (redis.asyncio) for pub/sub
- Validation: Pydantic v2.12.5 with ORM serialization
- Testing: pytest 9.0.3 with asyncio plugin
- Python 3.12+
- Redis 7.0+ (or comment out Redis config for single-server mode)
# Clone the repository
git clone <repo>
cd fast-broadcast
# Create virtual environment
python -m venv .venv
source .venv/bin/activate # or .venv\Scripts\activate on Windows
# Install dependencies
pip install -r requirements.txt
# Set environment variables (optional)
export REDIS_URL=redis://localhost:6379 # Defaults to localhost:6379
export DATABASE_URL=sqlite:///./test.db
# Run the server
python -m uvicorn main:app --reload
# Run tests
pytest -qPOST /auth/register- Register new userPOST /auth/token- Login and get JWT token
GET /users/{user_id}/online- Check if user is online globallyGET /online-users- List all globally online users with usernames
POST /rooms- Create a room (public/private)GET /rooms- List rooms (public + member rooms)GET /rooms/{room_id}- Get room detailsPOST /rooms/{room_id}/join- Join a roomPOST /rooms/{room_id}/leave- Leave a roomGET /rooms/{room_id}/members- List room membersGET /rooms/{room_id}/online-users- List online users in room with usernamesGET /rooms/{room_id}/users/{user_id}/online- Check if user is online in room with username
WS /ws- Connect to WebSocket for real-time messaging- Send events:
room.join,room.leave,room.message - Receive events:
room.message,room.join,room.leave,room.presence_update,error
- Send events:
# Include JWT token in Authorization header
ws://localhost:8000/ws?token=<jwt_token>
# or
headers: {"Authorization": "Bearer <jwt_token>"}{
"event": "room.message",
"room_id": 1,
"payload": {"message": "hello"}
}{
"event": "room.join",
"room_id": 1,
"payload": {}
}On join, you receive a room.presence_update event with all currently online users in the room:
{
"event": "room.presence_update",
"room_id": 1,
"sender": {...},
"payload": {
"online_users": [
{"user_id": 2, "username": "bob", "is_online": true}
]
}
}{
"event": "room.leave",
"room_id": 1,
"payload": {}
}id(PK)username(unique, string)email(unique, email)hashed_password(string)is_active(boolean)created_at,updated_at(timestamps)
id(PK)name(unique case-insensitive, string)visibility(enum: public/private)owner_user_id(FK → User)is_active(boolean)created_at,updated_at(timestamps)
id(PK)room_id(FK → Room, cascade delete)user_id(FK → User, cascade delete)role(enum: owner/member)joined_at(timestamp)left_at(timestamp, nullable - soft delete)invited_by_user_id(FK → User, nullable)last_read_at(timestamp, nullable)- Constraints:
- Partial unique index on (room_id, user_id) where left_at IS NULL
- Partial unique index on (room_id) where role='owner' and left_at IS NULL (one owner per room)
presence:online_users- Set of online user IDspresence:user:{user_id}:connections- Connection counter per user
presence:room:{room_id}:online_users- Set of online user IDs in room
- Channel:
broadcast- Single pub/sub channel for all room messages- Payload includes
room_idfor filtering
- Payload includes
All tests are in the tests/ directory:
test_auth_endpoints.py- Registration and logintest_presence_endpoints.py- Global and room presencetest_room_endpoints.py- Room CRUD and membershiptest_websocket_endpoint.py- WebSocket event handling
Run tests:
pytest -q # Quick summary
pytest -v # Verbose
pytest tests/test_auth_endpoints.py # Single fileCurrent: 23 tests passing
- Message persistence and history retrieval
- Message editing/deletion
- User invitations via REST endpoint
- Room transfer ownership endpoint
- Rate limiting and message validation
- WebSocket disconnect recovery
MIT