Building a Real-Time WebSocket Dashboard with LiveKit and Flask
Building a Real-Time WebSocket Dashboard with LiveKit and Flask
The demand for real-time data visualization has never been higher. Whether you're monitoring IoT sensor networks, tracking financial tickers, or observing live system logs, users expect dashboards that update instantaneously without requiring manual page refreshes. This article explores how to build a high-performance real-time dashboard that combines the simplicity of Flask with LiveKit's powerful WebRTC infrastructure to deliver low-latency, bi-directional communication.
The Challenge of Real-Time Data Streaming
Traditional client-server architectures rely on HTTP polling, where the client repeatedly requests updates from the server. This approach is inefficient—it wastes bandwidth, increases server load, and introduces latency proportional to the polling interval. For applications requiring sub-second updates, polling simply doesn't suffice.
WebSockets provide a persistent, full-duplex communication channel, but they only solve half the problem. When you need to stream high-frequency data like sensor readings or video feeds, WebRTC emerges as the superior choice. WebRTC offers peer-to-peer data channels with minimal latency and handles NAT traversal, bandwidth estimation, and packet loss recovery automatically.
This is where LiveKit enters the picture.
Understanding LiveKit's Architecture
LiveKit is a "batteries-included" WebRTC infrastructure that abstracts the low-level complexities of media transport while exposing rigorous control via SDKs . It follows a distributed architecture with clear separation of concerns:
- LiveKit Server (Go): The Selective Forwarding Unit (SFU) that handles RTP packets, bandwidth estimation, and stream forwarding
- Client SDKs: Libraries running on user devices (React, Swift, Kotlin) that handle device capture and WebRTC handshakes
- Server SDKs (Python/Go/Node): Your application's control plane that communicates with the LiveKit server via high-performance Twirp RPC
This architectural pattern allows your Python backend to focus on being the Orchestrator—provisioning rooms, minting security tokens, and triggering recordings—while LiveKit handles the data plane.
System Architecture Overview
Our real-time dashboard solution combines several modern technologies:
text:
┌─────────────────────────────────────────────────────────────┐
│ Browser Client │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ Chart.js │ │ LiveKit │ │ Dashboard UI │ │
│ │ Live Graphs │ │ Client SDK │ │ (HTML/CSS/JS) │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
└────────────────────────┬────────────────────────────────────┘
│ WebSocket/WebRTC
┌────────────────────────▼────────────────────────────────────┐
│ Flask Application │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ Auth │ │ Room │ │ Data Ingestion │ │
│ │ Management │ │ Management │ │ Endpoints │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
└────────────────────────┬────────────────────────────────────┘
│
┌────────────────────────▼────────────────────────────────────┐
│ LiveKit Server (WebRTC SFU) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ Signaling │ │ Media │ │ TURN/STUN │ │
│ │ Handler │ │ Forwarding │ │ Relay │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
┌────────────────────────▼────────────────────────────────────┐
│ Redis (Pub/Sub) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ Telemetry │ │ Historical │ │ Alert State │ │
│ │ Channels │ │ Time Series │ │ Management │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Setting Up the Flask Backend
Dependencies
Start with the essential dependencies:
bash:
pip install Flask flask-socketio livekit-api redis uvicorn eventlet
JWT Authentication with LiveKit
LiveKit delegates authentication entirely to your backend. The LiveKit server has no user database—instead, it relies on JSON Web Tokens (JWT) signed with an API Key and Secret shared between your Python backend and the LiveKit server.
Here's how to generate tokens:
python:
import os
from livekit import api
def create_participant_token(room_name: str, participant_identity: str):
grant = api.VideoGrants(
room_join=True,
room=room_name,
can_publish=True,
can_subscribe=True,
)
token = api.AccessToken() \
.with_identity(participant_identity) \
.with_name(f"User {participant_identity}") \
.with_grants(grant) \
.with_ttl(3600) # 1 hour expiration
return token.to_jwt()
Critical architectural note: Never generate tokens on the client. Always generate them server-side. This allows you to revoke access, enforce bans, or dynamically assign permissions based on business logic.
Room Lifecycle Management
Production systems often require explicit room provisioning. Create rooms before participants join, set specific timeouts, or limit maximum participants:
python:
import asyncio
from livekit import api
async def provision_meeting_room(meeting_id: str):
lk_api = api.LiveKitAPI(
host=os.environ.get("LIVEKIT_HOST"),
api_key=os.environ.get("LIVEKIT_API_KEY"),
api_secret=os.environ.get("LIVEKIT_API_SECRET"),
)
# Create room with specific configuration
await lk_api.room.create_room(
name=f"dashboard-{meeting_id}",
empty_timeout=300, # Auto-close after 5 minutes empty
max_participants=50,
)
Real-Time Data Pipeline with Redis
Redis serves as the message backbone for our real-time dashboard, providing three essential primitives: hashes for current device states, sorted sets for time-series history, and Pub/Sub for pushing live updates to WebSocket clients.
Publishing Sensor Data
When telemetry data arrives (from IoT devices, financial APIs, or log streams), publish it to Redis:
python:
import json
import redis
r = redis.Redis(host="localhost", port=6379, decode_responses=True)
def update_and_publish(device_id, readings):
# Store current state
r.hset(f"device:{device_id}", mapping=readings)
# Store historical data in time-series sorted set
timestamp = int(time.time())
r.zadd(f"history:{device_id}", {json.dumps(readings): timestamp})
# Publish to live channel
event = json.dumps({"device_id": device_id, "readings": readings})
r.publish("telemetry:live", event)
Subscribing and Broadcasting to Clients
The Flask SocketIO server subscribes to Redis channels and forwards updates to connected browser clients:
python:
from flask_socketio import SocketIO, emit
socketio = SocketIO(app, cors_allowed_origins="*", async_mode="eventlet")
def telemetry_broadcaster():
pubsub = r.pubsub()
pubsub.subscribe("telemetry:live", "alerts:triggered")
for message in pubsub.listen():
if message["type"] == "message":
# Broadcast to all connected dashboard clients
socketio.emit("telemetry_update", message["data"])
This pattern ensures updates reach the browser with minimal latency, without requiring any database queries on the hot path.
The Browser Dashboard
Chart.js for Live Graphs
Chart.js provides elegant, responsive charts that update in real-time. The dashboard subscribes to the WebSocket connection and appends new data points as they arrive:
javascript:
const ctx = document.getElementById('liveChart').getContext('2d');
const chart = new Chart(ctx, {
type: 'line',
data: {
labels: [],
datasets: [{
label: 'Sensor Data',
data: [],
borderColor: 'rgb(75, 192, 192)',
tension: 0.1
}]
},
options: {
animation: false, // Disable for smoother real-time updates
responsive: true,
maintainAspectRatio: false
}
});
// Socket connection for real-time updates
const socket = io();
socket.on('telemetry_update', function(data) {
const readings = JSON.parse(data);
chart.data.labels.push(new Date().toLocaleTimeString());
chart.data.datasets[0].data.push(readings.value);
// Keep only last 50 data points
if (chart.data.labels.length > 50) {
chart.data.labels.shift();
chart.data.datasets[0].data.shift();
}
chart.update('none'); // 'none' skip animation for performance
});
Connecting to LiveKit
For applications requiring streaming media or peer-to-peer data channels, the LiveKit client SDK connects to the LiveKit server using the token generated by your backend:
javascript:
import { Room, RoomEvent } from 'livekit-client';
const room = new Room();
room.on(RoomEvent.TrackSubscribed, (track) => {
// Handle incoming media or data tracks
});
// Connect using token from your Flask backend
const token = await fetch('/api/token', {
method: 'POST',
body: JSON.stringify({ roomName: 'dashboard-room' })
}).then(r => r.json());
await room.connect('wss://your-livekit-server.com', token);
Real-World Implementation Examples
The architecture described here has been successfully implemented in various production applications:
Synth, a real-time voice channel app inspired by Discord, uses React 19 with Flask and LiveKit WebRTC. It implements role-based access control with server-scoped permissions, JWT authentication with bcrypt password hashing, and a cyberpunk neon UI—all deployed as a single Docker container.
LearnAloud, an AI voice tutor that reads academic papers aloud, uses Flask with LiveKit for voice-synchronized PDF highlighting and real-time interactions. The application tracks coverage metrics, engagement depth scores, and supports push-to-talk voice interactions with AI agents.
Liveflow, a real-time debugging dashboard for LiveKit voice agents, demonstrates how LiveKit's event system can be used to build powerful monitoring tools. It captures agent state changes, tool calls, handoffs, and transcripts, displaying them in a VS Code extension interface.
Production Considerations
Performance Optimization
For high-volume deployments, consider these optimizations:
- Use Uvicorn with ASGI support to handle concurrent WebSocket connections efficiently
- Implement connection pooling for Redis to reduce overhead
- Buffer data on the server-side during high-load periods
- Enable compression for WebSocket messages when bandwidth is constrained
Security
- Always validate and sanitize incoming data before broadcasting
- Implement rate limiting for data ingestion endpoints
- Store API keys and secrets in environment variables, never in code
- Use HTTPS/WSS in production environments
Scalability
The architecture is horizontally scalable:
- Deploy multiple Flask instances behind a load balancer
- Use Redis as a centralized Pub/Sub broker for cross-instance communication
- Deploy LiveKit servers in a cluster configuration for media routing redundancy
Conclusion
Building a real-time dashboard with Flask and LiveKit creates a production-ready architecture capable of handling everything from IoT sensor data to complex collaborative applications. The combination of Flask's simplicity, LiveKit's robust WebRTC infrastructure, and Redis's Pub/Sub capabilities delivers low-latency, bi-directional communication at scale.
The separation of control plane (your Flask application) from data plane (LiveKit server) allows each component to scale independently, while JWT-based authentication ensures secure access. Whether you're building a telemetry dashboard, a live analytics platform, or a collaborative application, this architecture provides the foundation for real-time, responsive user experiences.
