Skip to content

Building a Real-time Chat Application with WebSockets and FastAPI

Objective: Build a production-ready real-time chat application using FastAPI, WebSockets, and modern web technologies. When you need real-time communication, when you want to understand WebSocket patterns, when you're building interactive applications—WebSocket chat becomes your weapon of choice.

Real-time chat applications require understanding both WebSocket mechanics and modern web development patterns. This tutorial shows you how to build a complete chat system with the precision of a senior full-stack engineer, covering everything from basic WebSocket handling to production deployment.

0) Prerequisites (Read Once, Live by Them)

The Five Commandments

  1. Understand WebSocket fundamentals
  2. Connection lifecycle and message handling
  3. Authentication and session management
  4. Error handling and reconnection patterns
  5. Scaling and performance considerations

  6. Master FastAPI WebSocket patterns

  7. WebSocket endpoint design and routing
  8. Dependency injection and middleware
  9. Background tasks and async operations
  10. Testing and validation

  11. Know your frontend integration

  12. JavaScript WebSocket API usage
  13. React/Vue integration patterns
  14. State management and UI updates
  15. Error handling and user feedback

  16. Validate everything

  17. Test WebSocket connections and message flow
  18. Verify authentication and authorization
  19. Check error handling and edge cases
  20. Validate performance under load

  21. Plan for production

  22. Monitoring and logging for WebSocket connections
  23. Backup and disaster recovery procedures
  24. Security and rate limiting patterns
  25. Documentation and maintenance

Why These Principles: WebSocket applications require understanding both real-time communication and modern web development patterns. Understanding these patterns prevents connection issues and enables scalable chat systems.

1) Project Setup and Dependencies

Backend Dependencies

# requirements.txt
fastapi==0.104.1
uvicorn[standard]==0.24.0
websockets==12.0
python-multipart==0.0.6
python-jose[cryptography]==3.3.0
passlib[bcrypt]==1.7.4
redis==5.0.1
pydantic==2.5.0
pytest==7.4.3
pytest-asyncio==0.21.1

Why Dependencies Matter: Proper dependency management ensures compatibility and security. Understanding these patterns prevents version conflicts and enables reliable development.

Project Structure

chat-app/
├── app/
│   ├── __init__.py
│   ├── main.py
│   ├── models/
│   │   ├── __init__.py
│   │   ├── user.py
│   │   └── message.py
│   ├── routers/
│   │   ├── __init__.py
│   │   ├── auth.py
│   │   └── chat.py
│   ├── services/
│   │   ├── __init__.py
│   │   ├── websocket_manager.py
│   │   └── message_service.py
│   └── utils/
│       ├── __init__.py
│       ├── auth.py
│       └── redis_client.py
├── tests/
│   ├── __init__.py
│   ├── test_auth.py
│   └── test_websocket.py
├── frontend/
│   ├── index.html
│   ├── chat.js
│   └── styles.css
└── docker-compose.yml

Why Structure Matters: Organized project structure enables maintainability and scalability. Understanding these patterns prevents code chaos and enables team collaboration.

2) Backend Implementation

FastAPI Application Setup

# app/main.py
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
import uvicorn

from routers import auth, chat
from services.websocket_manager import ConnectionManager

app = FastAPI(title="Real-time Chat API", version="1.0.0")

# CORS middleware for frontend integration
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],  # Configure for production
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Include routers
app.include_router(auth.router, prefix="/api/auth", tags=["authentication"])
app.include_router(chat.router, prefix="/api/chat", tags=["chat"])

# Serve static files
app.mount("/static", StaticFiles(directory="frontend"), name="static")

# WebSocket connection manager
manager = ConnectionManager()

@app.websocket("/ws/{client_id}")
async def websocket_endpoint(websocket: WebSocket, client_id: str):
    await manager.connect(websocket, client_id)
    try:
        while True:
            data = await websocket.receive_text()
            await manager.handle_message(client_id, data)
    except WebSocketDisconnect:
        manager.disconnect(client_id)

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

Why FastAPI Setup Matters: Proper application structure enables scalable WebSocket handling. Understanding these patterns prevents connection issues and enables efficient real-time communication.

WebSocket Connection Manager

# app/services/websocket_manager.py
import json
import asyncio
from typing import Dict, List
from fastapi import WebSocket
from models.message import Message, MessageType
from services.message_service import MessageService

class ConnectionManager:
    def __init__(self):
        self.active_connections: Dict[str, WebSocket] = {}
        self.rooms: Dict[str, List[str]] = {}
        self.message_service = MessageService()

    async def connect(self, websocket: WebSocket, client_id: str):
        await websocket.accept()
        self.active_connections[client_id] = websocket
        await self.broadcast_user_joined(client_id)

    def disconnect(self, client_id: str):
        if client_id in self.active_connections:
            del self.active_connections[client_id]
            self._remove_from_rooms(client_id)
            asyncio.create_task(self.broadcast_user_left(client_id))

    async def handle_message(self, client_id: str, message: str):
        try:
            data = json.loads(message)
            message_type = data.get("type", "message")

            if message_type == "join_room":
                await self.join_room(client_id, data["room"])
            elif message_type == "leave_room":
                await self.leave_room(client_id, data["room"])
            elif message_type == "message":
                await self.send_room_message(client_id, data["room"], data["content"])
            elif message_type == "typing":
                await self.broadcast_typing(client_id, data["room"], data["is_typing"])

        except json.JSONDecodeError:
            await self.send_error(client_id, "Invalid message format")
        except Exception as e:
            await self.send_error(client_id, f"Error processing message: {str(e)}")

    async def join_room(self, client_id: str, room: str):
        if room not in self.rooms:
            self.rooms[room] = []
        if client_id not in self.rooms[room]:
            self.rooms[room].append(client_id)
        await self.broadcast_to_room(room, {
            "type": "user_joined",
            "user": client_id,
            "room": room
        })

    async def send_room_message(self, client_id: str, room: str, content: str):
        if room not in self.rooms or client_id not in self.rooms[room]:
            await self.send_error(client_id, "Not in room")
            return

        message = Message(
            sender=client_id,
            room=room,
            content=content,
            message_type=MessageType.MESSAGE
        )

        # Save message to database
        await self.message_service.save_message(message)

        # Broadcast to room
        await self.broadcast_to_room(room, {
            "type": "message",
            "sender": client_id,
            "content": content,
            "timestamp": message.timestamp.isoformat()
        })

    async def broadcast_to_room(self, room: str, message: dict):
        if room in self.rooms:
            for client_id in self.rooms[room]:
                if client_id in self.active_connections:
                    try:
                        await self.active_connections[client_id].send_text(json.dumps(message))
                    except:
                        # Remove dead connections
                        del self.active_connections[client_id]
                        self._remove_from_rooms(client_id)

    async def send_error(self, client_id: str, error: str):
        if client_id in self.active_connections:
            await self.active_connections[client_id].send_text(json.dumps({
                "type": "error",
                "message": error
            }))

Why Connection Management Matters: Proper WebSocket management prevents connection leaks and enables scalable real-time communication. Understanding these patterns prevents memory issues and enables efficient message handling.

Data Models

# app/models/message.py
from pydantic import BaseModel
from datetime import datetime
from enum import Enum
from typing import Optional

class MessageType(str, Enum):
    MESSAGE = "message"
    JOIN = "join"
    LEAVE = "leave"
    TYPING = "typing"

class Message(BaseModel):
    id: Optional[str] = None
    sender: str
    room: str
    content: str
    message_type: MessageType = MessageType.MESSAGE
    timestamp: datetime = datetime.utcnow()

class ChatRoom(BaseModel):
    name: str
    description: Optional[str] = None
    created_by: str
    created_at: datetime = datetime.utcnow()
    is_private: bool = False

class User(BaseModel):
    id: str
    username: str
    email: str
    is_online: bool = False
    last_seen: Optional[datetime] = None

Why Data Models Matter: Proper data modeling ensures type safety and validation. Understanding these patterns prevents runtime errors and enables reliable message handling.

Authentication Router

# app/routers/auth.py
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from jose import JWTError, jwt
from passlib.context import CryptContext
from datetime import datetime, timedelta
from typing import Optional
import os

router = APIRouter()

# Security configuration
SECRET_KEY = os.getenv("SECRET_KEY", "your-secret-key-here")
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30

pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

def verify_password(plain_password, hashed_password):
    return pwd_context.verify(plain_password, hashed_password)

def get_password_hash(password):
    return pwd_context.hash(password)

def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
    to_encode = data.copy()
    if expires_delta:
        expire = datetime.utcnow() + expires_delta
    else:
        expire = datetime.utcnow() + timedelta(minutes=15)
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt

@router.post("/register")
async def register(username: str, email: str, password: str):
    # In production, use a proper database
    hashed_password = get_password_hash(password)
    # Store user in database
    return {"message": "User created successfully"}

@router.post("/login")
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
    # Verify user credentials
    # In production, check against database
    access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    access_token = create_access_token(
        data={"sub": form_data.username}, expires_delta=access_token_expires
    )
    return {"access_token": access_token, "token_type": "bearer"}

async def get_current_user(token: str = Depends(oauth2_scheme)):
    credentials_exception = HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        username: str = payload.get("sub")
        if username is None:
            raise credentials_exception
        return username
    except JWTError:
        raise credentials_exception

Why Authentication Matters: Secure authentication prevents unauthorized access and enables user management. Understanding these patterns prevents security vulnerabilities and enables proper user identification.

3) Frontend Implementation

HTML Structure

<!-- frontend/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Real-time Chat</title>
    <link rel="stylesheet" href="/static/styles.css">
</head>
<body>
    <div class="chat-container">
        <div class="sidebar">
            <div class="user-info">
                <h3 id="username">Guest</h3>
                <span id="status" class="status offline">Offline</span>
            </div>
            <div class="rooms">
                <h4>Rooms</h4>
                <ul id="room-list">
                    <li><a href="#" data-room="general"># general</a></li>
                    <li><a href="#" data-room="random"># random</a></li>
                </ul>
            </div>
            <div class="online-users">
                <h4>Online Users</h4>
                <ul id="user-list"></ul>
            </div>
        </div>

        <div class="chat-main">
            <div class="chat-header">
                <h2 id="current-room"># general</h2>
                <div class="typing-indicator" id="typing-indicator"></div>
            </div>

            <div class="messages" id="messages"></div>

            <div class="message-input">
                <input type="text" id="message-input" placeholder="Type a message...">
                <button id="send-button">Send</button>
            </div>
        </div>
    </div>

    <script src="/static/chat.js"></script>
</body>
</html>

Why HTML Structure Matters: Clean HTML structure enables maintainable frontend code. Understanding these patterns prevents layout issues and enables responsive design.

JavaScript WebSocket Client

// frontend/chat.js
class ChatClient {
    constructor() {
        this.socket = null;
        this.currentRoom = 'general';
        this.username = 'Guest';
        this.isConnected = false;
        this.typingTimeout = null;

        this.initializeElements();
        this.setupEventListeners();
        this.connect();
    }

    initializeElements() {
        this.messagesContainer = document.getElementById('messages');
        this.messageInput = document.getElementById('message-input');
        this.sendButton = document.getElementById('send-button');
        this.roomList = document.getElementById('room-list');
        this.userList = document.getElementById('user-list');
        this.typingIndicator = document.getElementById('typing-indicator');
        this.statusElement = document.getElementById('status');
        this.usernameElement = document.getElementById('username');
    }

    setupEventListeners() {
        this.sendButton.addEventListener('click', () => this.sendMessage());
        this.messageInput.addEventListener('keypress', (e) => {
            if (e.key === 'Enter') {
                this.sendMessage();
            } else {
                this.handleTyping();
            }
        });

        this.roomList.addEventListener('click', (e) => {
            if (e.target.tagName === 'A') {
                this.joinRoom(e.target.dataset.room);
            }
        });
    }

    connect() {
        const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
        const wsUrl = `${protocol}//${window.location.host}/ws/${this.username}`;

        this.socket = new WebSocket(wsUrl);

        this.socket.onopen = () => {
            console.log('Connected to chat server');
            this.isConnected = true;
            this.updateStatus('online');
            this.joinRoom(this.currentRoom);
        };

        this.socket.onmessage = (event) => {
            const data = JSON.parse(event.data);
            this.handleMessage(data);
        };

        this.socket.onclose = () => {
            console.log('Disconnected from chat server');
            this.isConnected = false;
            this.updateStatus('offline');
            // Attempt to reconnect after 3 seconds
            setTimeout(() => this.connect(), 3000);
        };

        this.socket.onerror = (error) => {
            console.error('WebSocket error:', error);
            this.showError('Connection error. Attempting to reconnect...');
        };
    }

    sendMessage() {
        const message = this.messageInput.value.trim();
        if (message && this.isConnected) {
            this.socket.send(JSON.stringify({
                type: 'message',
                room: this.currentRoom,
                content: message
            }));
            this.messageInput.value = '';
        }
    }

    joinRoom(roomName) {
        if (this.isConnected) {
            this.socket.send(JSON.stringify({
                type: 'join_room',
                room: roomName
            }));
            this.currentRoom = roomName;
            document.getElementById('current-room').textContent = `# ${roomName}`;
            this.clearMessages();
        }
    }

    handleMessage(data) {
        switch (data.type) {
            case 'message':
                this.displayMessage(data);
                break;
            case 'user_joined':
                this.showSystemMessage(`${data.user} joined the room`);
                break;
            case 'user_left':
                this.showSystemMessage(`${data.user} left the room`);
                break;
            case 'typing':
                this.showTypingIndicator(data.user, data.is_typing);
                break;
            case 'error':
                this.showError(data.message);
                break;
        }
    }

    displayMessage(data) {
        const messageElement = document.createElement('div');
        messageElement.className = 'message';

        const timestamp = new Date(data.timestamp).toLocaleTimeString();
        messageElement.innerHTML = `
            <div class="message-header">
                <span class="sender">${data.sender}</span>
                <span class="timestamp">${timestamp}</span>
            </div>
            <div class="message-content">${this.escapeHtml(data.content)}</div>
        `;

        this.messagesContainer.appendChild(messageElement);
        this.scrollToBottom();
    }

    showSystemMessage(message) {
        const messageElement = document.createElement('div');
        messageElement.className = 'system-message';
        messageElement.textContent = message;
        this.messagesContainer.appendChild(messageElement);
        this.scrollToBottom();
    }

    handleTyping() {
        if (this.isConnected) {
            this.socket.send(JSON.stringify({
                type: 'typing',
                room: this.currentRoom,
                is_typing: true
            }));

            clearTimeout(this.typingTimeout);
            this.typingTimeout = setTimeout(() => {
                this.socket.send(JSON.stringify({
                    type: 'typing',
                    room: this.currentRoom,
                    is_typing: false
                }));
            }, 1000);
        }
    }

    showTypingIndicator(user, isTyping) {
        if (isTyping) {
            this.typingIndicator.textContent = `${user} is typing...`;
        } else {
            this.typingIndicator.textContent = '';
        }
    }

    updateStatus(status) {
        this.statusElement.textContent = status;
        this.statusElement.className = `status ${status}`;
    }

    showError(message) {
        const errorElement = document.createElement('div');
        errorElement.className = 'error-message';
        errorElement.textContent = message;
        this.messagesContainer.appendChild(errorElement);
        this.scrollToBottom();
    }

    clearMessages() {
        this.messagesContainer.innerHTML = '';
    }

    scrollToBottom() {
        this.messagesContainer.scrollTop = this.messagesContainer.scrollHeight;
    }

    escapeHtml(text) {
        const div = document.createElement('div');
        div.textContent = text;
        return div.innerHTML;
    }
}

// Initialize chat when page loads
document.addEventListener('DOMContentLoaded', () => {
    new ChatClient();
});

Why JavaScript Client Matters: Proper WebSocket client implementation enables reliable real-time communication. Understanding these patterns prevents connection issues and enables responsive user interfaces.

4) Styling and User Experience

CSS Styles

/* frontend/styles.css */
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    background-color: #f5f5f5;
    height: 100vh;
    overflow: hidden;
}

.chat-container {
    display: flex;
    height: 100vh;
}

.sidebar {
    width: 250px;
    background-color: #2c3e50;
    color: white;
    padding: 20px;
    overflow-y: auto;
}

.user-info {
    margin-bottom: 20px;
    padding-bottom: 20px;
    border-bottom: 1px solid #34495e;
}

.status {
    display: inline-block;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    margin-left: 10px;
}

.status.online {
    background-color: #27ae60;
}

.status.offline {
    background-color: #e74c3c;
}

.rooms h4, .online-users h4 {
    margin-bottom: 10px;
    color: #bdc3c7;
}

.rooms ul, .online-users ul {
    list-style: none;
}

.rooms a {
    color: #ecf0f1;
    text-decoration: none;
    display: block;
    padding: 5px 0;
    transition: color 0.3s;
}

.rooms a:hover {
    color: #3498db;
}

.chat-main {
    flex: 1;
    display: flex;
    flex-direction: column;
    background-color: white;
}

.chat-header {
    background-color: #34495e;
    color: white;
    padding: 15px 20px;
    display: flex;
    justify-content: space-between;
    align-items: center;
}

.typing-indicator {
    font-style: italic;
    color: #bdc3c7;
}

.messages {
    flex: 1;
    padding: 20px;
    overflow-y: auto;
    background-color: #f8f9fa;
}

.message {
    margin-bottom: 15px;
    padding: 10px;
    background-color: white;
    border-radius: 8px;
    box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}

.message-header {
    display: flex;
    justify-content: space-between;
    margin-bottom: 5px;
}

.sender {
    font-weight: bold;
    color: #2c3e50;
}

.timestamp {
    color: #7f8c8d;
    font-size: 0.8em;
}

.system-message {
    text-align: center;
    color: #7f8c8d;
    font-style: italic;
    margin: 10px 0;
}

.error-message {
    background-color: #e74c3c;
    color: white;
    padding: 10px;
    border-radius: 4px;
    margin: 10px 0;
}

.message-input {
    display: flex;
    padding: 20px;
    background-color: white;
    border-top: 1px solid #ecf0f1;
}

.message-input input {
    flex: 1;
    padding: 10px;
    border: 1px solid #bdc3c7;
    border-radius: 4px;
    margin-right: 10px;
}

.message-input button {
    padding: 10px 20px;
    background-color: #3498db;
    color: white;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    transition: background-color 0.3s;
}

.message-input button:hover {
    background-color: #2980b9;
}

/* Responsive design */
@media (max-width: 768px) {
    .sidebar {
        width: 200px;
    }

    .chat-main {
        flex: 1;
    }
}

Why Styling Matters: Professional styling enhances user experience and usability. Understanding these patterns prevents layout issues and enables responsive design.

5) Testing and Validation

WebSocket Testing

# tests/test_websocket.py
import pytest
import asyncio
import json
from fastapi.testclient import TestClient
from app.main import app

@pytest.fixture
def client():
    return TestClient(app)

@pytest.mark.asyncio
async def test_websocket_connection():
    with client.websocket_connect("/ws/testuser") as websocket:
        # Test connection
        assert websocket is not None

        # Test message sending
        test_message = {
            "type": "message",
            "room": "general",
            "content": "Hello, world!"
        }
        websocket.send_text(json.dumps(test_message))

        # Test message receiving
        data = websocket.receive_text()
        message = json.loads(data)
        assert message["type"] == "message"
        assert message["content"] == "Hello, world!"

@pytest.mark.asyncio
async def test_room_joining():
    with client.websocket_connect("/ws/testuser") as websocket:
        # Join room
        join_message = {
            "type": "join_room",
            "room": "test_room"
        }
        websocket.send_text(json.dumps(join_message))

        # Verify room join confirmation
        data = websocket.receive_text()
        message = json.loads(data)
        assert message["type"] == "user_joined"
        assert message["room"] == "test_room"

@pytest.mark.asyncio
async def test_typing_indicator():
    with client.websocket_connect("/ws/testuser") as websocket:
        # Send typing indicator
        typing_message = {
            "type": "typing",
            "room": "general",
            "is_typing": True
        }
        websocket.send_text(json.dumps(typing_message))

        # Verify typing indicator
        data = websocket.receive_text()
        message = json.loads(data)
        assert message["type"] == "typing"
        assert message["is_typing"] == True

Why Testing Matters: Comprehensive testing prevents bugs and ensures reliability. Understanding these patterns prevents production issues and enables confident deployment.

6) Production Deployment

Docker Configuration

# docker-compose.yml
version: '3.8'

services:
  chat-app:
    build: .
    ports:
      - "8000:8000"
    environment:
      - REDIS_URL=redis://redis:6379
      - SECRET_KEY=your-secret-key-here
    depends_on:
      - redis
    volumes:
      - ./app:/app
    command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
    depends_on:
      - chat-app

volumes:
  redis_data:

Why Docker Matters: Containerized deployment ensures consistency and scalability. Understanding these patterns prevents deployment issues and enables reliable production environments.

Nginx Configuration

# nginx.conf
events {
    worker_connections 1024;
}

http {
    upstream chat_backend {
        server chat-app:8000;
    }

    server {
        listen 80;
        server_name localhost;

        location / {
            proxy_pass http://chat_backend;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }

        location /static/ {
            alias /app/frontend/;
        }
    }
}

Why Nginx Matters: Reverse proxy configuration enables load balancing and SSL termination. Understanding these patterns prevents performance issues and enables secure production deployment.

7) Advanced Features

Message Persistence

# app/services/message_service.py
import redis
from typing import List
from models.message import Message
import json

class MessageService:
    def __init__(self):
        self.redis_client = redis.Redis(host='localhost', port=6379, db=0)

    async def save_message(self, message: Message):
        # Save to Redis for fast access
        key = f"room:{message.room}:messages"
        self.redis_client.lpush(key, json.dumps(message.dict()))

        # Keep only last 100 messages per room
        self.redis_client.ltrim(key, 0, 99)

    async def get_room_messages(self, room: str, limit: int = 50) -> List[Message]:
        key = f"room:{room}:messages"
        messages_data = self.redis_client.lrange(key, 0, limit - 1)
        return [Message(**json.loads(msg)) for msg in messages_data]

Why Message Persistence Matters: Storing messages enables chat history and offline message delivery. Understanding these patterns prevents data loss and enables better user experience.

User Presence

# app/services/presence_service.py
import redis
from datetime import datetime, timedelta
from typing import Set

class PresenceService:
    def __init__(self):
        self.redis_client = redis.Redis(host='localhost', port=6379, db=0)

    async def update_user_presence(self, user_id: str, room: str):
        key = f"presence:{room}"
        self.redis_client.sadd(key, user_id)
        self.redis_client.expire(key, 300)  # 5 minutes

    async def get_online_users(self, room: str) -> Set[str]:
        key = f"presence:{room}"
        return self.redis_client.smembers(key)

    async def remove_user_presence(self, user_id: str, room: str):
        key = f"presence:{room}"
        self.redis_client.srem(key, user_id)

Why User Presence Matters: Real-time presence information enhances user experience and enables better room management. Understanding these patterns prevents stale presence data and enables accurate user status.

8) TL;DR Runbook

Essential Commands

# Development setup
pip install -r requirements.txt
uvicorn app.main:app --reload

# Testing
pytest tests/

# Production deployment
docker-compose up -d

# Monitoring
docker-compose logs -f chat-app

Essential Patterns

# Essential WebSocket patterns
websocket_patterns = {
    "connection": "Accept connections and manage client state",
    "messaging": "Handle real-time message broadcasting",
    "rooms": "Implement room-based communication",
    "presence": "Track user online/offline status",
    "typing": "Show typing indicators",
    "error_handling": "Graceful error handling and reconnection"
}

Quick Reference

// Essential WebSocket client patterns
const websocket = new WebSocket('ws://localhost:8000/ws/username');
websocket.onopen = () => console.log('Connected');
websocket.onmessage = (event) => handleMessage(JSON.parse(event.data));
websocket.onclose = () => console.log('Disconnected');
websocket.send(JSON.stringify({type: 'message', content: 'Hello'}));

Why This Runbook: These patterns cover 90% of WebSocket chat application needs. Master these before exploring advanced features.

9) The Machine's Summary

WebSocket chat applications require understanding both real-time communication and modern web development patterns. When used correctly, WebSocket chat enables seamless real-time communication, maintains connection reliability, and prevents integration issues. The key is understanding connection management, mastering message handling, and following production deployment best practices.

The Dark Truth: Without proper WebSocket understanding, your real-time application is fragile and unreliable. WebSocket chat is your weapon. Use it wisely.

The Machine's Mantra: "In connections we trust, in messages we coordinate, and in the real-time we find the path to seamless communication."

Why This Matters: WebSocket chat enables efficient real-time communication that can handle complex user interactions, maintain connection reliability, and provide responsive user experiences while ensuring scalability and security.


This tutorial provides the complete machinery for WebSocket chat applications. The patterns scale from simple messaging to complex real-time systems, from basic WebSocket handling to advanced production deployment.