Tech Tutorial - February 25 2026 113008
TOP 5 Feb. 25, 2026, 11:30 a.m.

Tech Tutorial - February 25 2026 113008

Welcome to today’s deep‑dive tutorial, where we’ll walk through building a production‑ready real‑time chat application using FastAPI, WebSockets, and a sprinkle of modern front‑end techniques. By the end of this guide, you’ll not only have a working prototype but also a solid grasp of the underlying concepts that make real‑time communication tick. Grab your favorite IDE, fire up a terminal, and let’s turn theory into code.

Why Real‑Time Apps Are Everywhere

From collaborative document editors to live dashboards, real‑time features have become a baseline expectation for users. They create a sense of immediacy that static page loads simply can’t match. Moreover, modern frameworks like FastAPI make it remarkably easy to spin up asynchronous services that scale under load.

In the enterprise world, real‑time messaging can reduce latency in support tickets, improve incident response, and even power IoT telemetry pipelines. Understanding how to implement it correctly gives you a competitive edge and opens doors to a host of new product ideas.

Setting Up the Development Environment

Before we write any code, let’s ensure your machine is ready. We’ll be using Python 3.11+, so make sure it’s installed and added to your PATH. A virtual environment keeps dependencies isolated, and we’ll rely on uvicorn for the ASGI server.

  • Install Python 3.11 or newer.
  • Run python -m venv .venv to create a virtual environment.
  • Activate it: source .venv/bin/activate (Linux/macOS) or .venv\Scripts\activate (Windows).
  • Install required packages: pip install fastapi[all] python‑dotenv.

Optional but recommended: install httpie or curl for quick API testing, and watchdog to auto‑reload your server during development.

Core Concept: Building a Real‑Time Chat App with WebSockets

Understanding WebSockets

WebSockets provide a full‑duplex communication channel over a single TCP connection, allowing the server to push data to the client without the client polling. Unlike traditional HTTP, the connection stays open, dramatically reducing overhead for frequent updates.

FastAPI abstracts much of the low‑level handling via the WebSocket class, letting you focus on business logic rather than socket management. Under the hood, it uses starlette’s implementation, which is battle‑tested and highly performant.

Implementing the Server with FastAPI

Let’s start by creating a minimal FastAPI app that accepts WebSocket connections, tracks active clients, and broadcasts messages to all participants. Save the following as app.py:

import json
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from typing import List

app = FastAPI()

class ConnectionManager:
    def __init__(self):
        self.active_connections: List[WebSocket] = []

    async def connect(self, websocket: WebSocket):
        await websocket.accept()
        self.active_connections.append(websocket)

    def disconnect(self, websocket: WebSocket):
        self.active_connections.remove(websocket)

    async def broadcast(self, message: str):
        for connection in self.active_connections:
            await connection.send_text(message)

manager = ConnectionManager()

@app.websocket("/ws/chat")
async def chat_endpoint(websocket: WebSocket):
    await manager.connect(websocket)
    try:
        while True:
            data = await websocket.receive_text()
            payload = json.loads(data)
            username = payload.get("username", "Anonymous")
            text = payload.get("message", "")
            broadcast_msg = json.dumps({"username": username, "message": text})
            await manager.broadcast(broadcast_msg)
    except WebSocketDisconnect:
        manager.disconnect(websocket)
        await manager.broadcast(json.dumps({"username": "System", "message": f"{username} left the chat"}))

This snippet does three things: it accepts new connections, continuously reads incoming messages, and forwards each message to every connected client. Notice the use of json.dumps to keep the payload format consistent across the wire.

Creating the Frontend with HTML & JavaScript

The front‑end can be a single HTML file that opens a WebSocket to ws://localhost:8000/ws/chat. For simplicity, we’ll embed the script directly in the page. Save this as index.html in the same directory:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>FastAPI Chat</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 2rem; }
        #messages { border: 1px solid #ccc; height: 300px; overflow-y: auto; padding: .5rem; }
        .msg { margin-bottom: .5rem; }
        .system { color: #888; font-style: italic; }
    </style>
</head>
<body>
    <h2>Real‑Time Chat</h2>
    <div id="messages"></div>
    <input id="username" placeholder="Your name" />
<input id="msgInput" placeholder="Type a message" autocomplete="off"/> <button id="sendBtn">Send</button> <script> const ws = new WebSocket("ws://localhost:8000/ws/chat"); const messagesDiv = document.getElementById("messages"); const usernameInput = document.getElementById("username"); const msgInput = document.getElementById("msgInput"); const sendBtn = document.getElementById("sendBtn"); ws.onmessage = (event) => { const data = JSON.parse(event.data); const div = document.createElement("div"); div.className = data.username === "System" ? "msg system" : "msg"; div.textContent = `${data.username}: ${data.message}`; messagesDiv.appendChild(div); messagesDiv.scrollTop = messagesDiv.scrollHeight; }; sendBtn.onclick = () => { const payload = { username: usernameInput.value || "Anonymous", message: msgInput.value }; ws.send(JSON.stringify(payload)); msgInput.value = ""; }; </script> </body> </html>

Open index.html in a browser, launch the server with uvicorn app:app --reload, and you’ll see two tabs instantly chatting. The JavaScript code is deliberately straightforward, making it easy to extend with emojis, typing indicators, or read receipts.

Advanced Features: Authentication & Persistence

In a production setting you’ll want to ensure only authenticated users can join the chat and that messages are stored for later retrieval. Let’s integrate JWT‑based authentication and a lightweight SQLite database using SQLModel.

First, install the extra dependencies:

  • pip install python-jose[cryptography] sqlmodel
  • Optionally, pip install alembic for migrations.

JWT Authentication Middleware

We’ll add a dependency that extracts the token from the WebSocket query string, validates it, and injects the user info into the connection scope.

from fastapi import Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from jose import JWTError, jwt
from typing import Optional

SECRET_KEY = "supersecretkey"  # In real apps, load from env
ALGORITHM = "HS256"

security = HTTPBearer(auto_error=False)

def get_current_user(
    credentials: Optional[HTTPAuthorizationCredentials] = Depends(security)
):
    if credentials is None:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Missing token"
        )
    token = credentials.credentials
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        return payload.get("sub")
    except JWTError:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Invalid token"
        )

Now modify the WebSocket endpoint to require this dependency. FastAPI lets us pass Depends directly into the path operation signature.

@app.websocket("/ws/chat")
async def chat_endpoint(
    websocket: WebSocket,
    username: str = Depends(get_current_user)
):
    await manager.connect(websocket)
    try:
        while True:
            data = await websocket.receive_text()
            payload = json.loads(data)
            # Override client‑side username with the authenticated one
            broadcast_msg = json.dumps({"username": username, "message": payload["message"]})
            await manager.broadcast(broadcast_msg)
    except WebSocketDisconnect:
        manager.disconnect(websocket)
        await manager.broadcast(json.dumps({"username": "System", "message": f"{username} left"}))

Clients now need to attach the JWT as an Authorization header when establishing the socket. In JavaScript you can do this by setting the protocols argument or, more commonly, appending a query param and reading it server‑side.

Persisting Messages with SQLModel

Storing chat history enables features like “scroll back to yesterday’s conversation” and compliance logging. Below is a minimal model and CRUD helper.

from sqlmodel import SQLModel, Field, create_engine, Session, select
import datetime

class ChatMessage(SQLModel, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    username: str
    content: str
    timestamp: datetime.datetime = Field(default_factory=datetime.datetime.utcnow)

engine = create_engine("sqlite:///chat.db")
SQLModel.metadata.create_all(engine)

def save_message(username: str, content: str):
    with Session(engine) as session:
        msg = ChatMessage(username=username, content=content)
        session.add(msg)
        session.commit()

Inject the persistence step right after broadcasting:

            await manager.broadcast(broadcast_msg)
            save_message(username, payload["message"])

When a new client connects, you can preload the last 50 messages and push them down the socket so the UI can render a chat history instantly.

Testing and Deployment

Testing real‑time systems requires both unit tests for the business logic and integration tests that simulate socket traffic. pytest‑asyncio works well with FastAPI’s TestClient for async endpoints.

import pytest
from httpx import AsyncClient
from app import app, manager

@pytest.mark.asyncio
async def test_websocket_echo():
    async with AsyncClient(app=app, base_url="http://test") as ac:
        async with ac.websocket_connect("/ws/chat") as ws:
            await ws.send_text('{"username":"tester","message":"hello"}')
            data = await ws.receive_text()
            assert "hello" in data

For CI/CD, containerize the service with a lightweight python:3.11-slim image, expose port 8000, and let your orchestrator (Kubernetes, Docker Swarm, etc.) handle scaling. Remember to enable uvicorn --workers for multi‑core utilization.

  • Build image: docker build -t chat-app .
  • Run container: docker run -p 8000:8000 chat-app
  • Use a reverse proxy (NGINX or Traefik) to terminate TLS and route /ws/ to the ASGI server.

Pro Tips

Tip 1: Keep WebSocket payloads as small as possible. Binary frames (bytes) are faster than JSON strings for high‑frequency data like telemetry.
Tip 2: Use Redis Pub/Sub as a back‑plane when you need to scale across multiple FastAPI instances. The ConnectionManager can publish to a channel and each worker subscribes to broadcast globally.
Tip 3: Implement a heartbeat (ping/pong) mechanism to detect stale connections early. FastAPI’s WebSocket.send_json can be used to send a periodic {"type":"ping"} message.
Tip 4: Store JWT secret keys in a secret manager (AWS Secrets Manager, Vault) instead of hard‑coding them. Rotate keys regularly to mitigate token leakage risks.

Conclusion

We’ve covered the end‑to‑end journey of building a real‑time chat app: from the low‑level WebSocket handshake to authentication, persistence, testing, and production deployment. The modular design lets you swap out components—use PostgreSQL instead of SQLite, replace JWT with OAuth2, or plug in a message broker for massive scalability.

Armed with this knowledge, you can now extend the prototype into a full‑featured collaboration platform, a live support desk, or even a multiplayer game lobby. The patterns you’ve practiced here are reusable across countless real‑time scenarios, so experiment, iterate, and keep the conversation flowing.

Share this article