Getting Started with Flask WebSockets
Flask, a popular Python minimalistic web framework, is highly regarded for its simplicity and flexibility in building web applications.
To enhance its capabilities, Flask can be extended with additional tools such as Flask-SocketIO, which transforms Flask applications by adding WebSocket support through the Socket.IO protocol.
This extension brings real-time bidirectional communication to Flask, enabling features like instant messaging, live updates, collaborative editing, and push notifications, all while maintaining Flask's lightweight philosophy.
This hands-on guide demonstrates how to integrate WebSocket functionality into Flask applications step by step.
Prerequisites
To follow along effectively, ensure you have Python 3.8 or newer installed. Familiarity with Flask fundamentals, such as routing, templates, and the request-response cycle, will aid your understanding. Some JavaScript knowledge is also helpful because we'll create interactive browser-based examples.
Setting up your Flask WebSocket project
In this section, you'll set up a project directory to organize your code. We'll start with a fresh setup to demonstrate proper integration of Flask-SocketIO.
To get started, create a directory, set up a virtual environment, and activate it to keep things tidy and prevent conflicts with system-wide packages:
mkdir flask_websockets && cd flask_websockets
python3 -m venv venv
source venv/bin/activate
Then install the required packages for WebSocket functionality:
pip install flask flask-socketio
Create your main Flask application with Socket.IO integration:
from flask import Flask, render_template
from flask_socketio import SocketIO
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key-here'
socketio = SocketIO(app, cors_allowed_origins="*")
@app.route('/')
def index():
return render_template('index.html')
if __name__ == '__main__':
socketio.run(app, debug=True, host='127.0.0.1', port=5000)
Flask-SocketIO requires a secret key for session management and creates a SocketIO instance that wraps your Flask app. The cors_allowed_origins="*"
setting permits connections from any domain during development. Notice we use socketio.run()
instead of app.run()
to start the server with WebSocket support.
Set up the templates directory:
mkdir templates
Create a real-time chat interface at templates/index.html
:
<!DOCTYPE html>
<html>
<head>
<title>Flask WebSocket Demo</title>
<style>
body {
font-family: system-ui, -apple-system, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
}
.chat-container {
background: white;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
overflow: hidden;
}
.chat-header {
background: #dc2626;
color: white;
padding: 15px 20px;
font-weight: 600;
}
#messages {
height: 400px;
overflow-y: auto;
padding: 20px;
border-bottom: 1px solid #e5e7eb;
}
.message {
margin-bottom: 10px;
padding: 8px 12px;
background: #f3f4f6;
border-radius: 6px;
max-width: 70%;
}
.message.own {
background: #dc2626;
color: white;
margin-left: auto;
text-align: right;
}
.message.system {
background: #fef3c7;
color: #92400e;
font-style: italic;
text-align: center;
max-width: 100%;
}
.input-container {
padding: 20px;
display: flex;
gap: 10px;
}
#messageInput {
flex: 1;
padding: 12px;
border: 1px solid #d1d5db;
border-radius: 6px;
font-size: 16px;
}
#sendButton {
padding: 12px 24px;
background: #dc2626;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: 500;
}
#sendButton:hover {
background: #b91c1c;
}
.status {
padding: 10px 20px;
background: #f3f4f6;
color: #6b7280;
font-size: 14px;
}
.status.connected {
background: #d1fae5;
color: #065f46;
}
</style>
</head>
<body>
<div class="chat-container">
<div class="chat-header">
<h1 style="margin: 0;">Flask WebSocket Chat</h1>
</div>
<div class="status" id="status">Connecting...</div>
<div id="messages"></div>
<div class="input-container">
<input type="text" id="messageInput" placeholder="Type your message..." maxlength="500">
<button id="sendButton">Send</button>
</div>
</div>
<script src="https://cdn.socket.io/4.8.1/socket.io.min.js"></script>
<script>
const socket = io();
const messages = document.getElementById('messages');
const messageInput = document.getElementById('messageInput');
const sendButton = document.getElementById('sendButton');
const status = document.getElementById('status');
socket.on('connect', function() {
status.textContent = 'Connected to chat server';
status.classList.add('connected');
console.log('Connected to server');
});
socket.on('connect_error', function(error) {
status.textContent = 'Connection failed';
status.classList.remove('connected');
console.log('Connection error:', error);
});
socket.on('disconnect', function(reason) {
status.textContent = 'Disconnected from chat server';
status.classList.remove('connected');
console.log('Disconnected from server:', reason);
});
socket.on('my response', function(data) {
addMessage(data.data, 'system');
});
function addMessage(message, sender) {
const messageElement = document.createElement('div');
messageElement.classList.add('message');
messageElement.textContent = message;
if (sender === 'self') {
messageElement.classList.add('own');
} else if (sender === 'system') {
messageElement.classList.add('system');
}
messages.appendChild(messageElement);
messages.scrollTop = messages.scrollHeight;
}
function sendMessage() {
const message = messageInput.value.trim();
if (message) {
socket.emit('my event', {message: message});
messageInput.value = '';
}
}
sendButton.addEventListener('click', sendMessage);
messageInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
sendMessage();
}
});
</script>
</body>
</html>
This client interface uses Socket.IO's JavaScript library to establish and manage the WebSocket connection.
The io()
function creates a connection to the server, while event listeners handle different connection states. connect
fires when the connection succeeds, connect_error
catches connection failures, and disconnect
triggers when the connection drops.
The socket.on('my response')
listener receives messages from the server and calls addMessage()
to display them in the chat. When users type messages, sendMessage()
validates the input and uses socket.emit('my event')
to send data to the server.
The automatic scrolling keeps the latest messages visible, while the status indicator provides real-time feedback about connection health.
Launch your Flask development server:
python app.py
The console output shows Flask-SocketIO initializing:
* Serving Flask app 'app'
* Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://127.0.0.1:5000
Press CTRL+C to quit
* Restarting with stat
* Debugger is active!
* Debugger PIN: 116-127-769
Navigate to http://127.0.0.1:5000
to view your chat interface:
The connection status should display "Connected to chat server" when the Socket.IO connection is established successfully.
Creating your first WebSocket event handlers
Flask-SocketIO organizes real-time communication around events, which are named messages that flow between clients and servers. This event-driven architecture makes it easy to handle different types of interactions while keeping your code organized and readable.
Add event handling to your Flask application by updating app.py
:
from flask import Flask, render_template, request
from flask_socketio import SocketIO, emit
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key-here'
socketio = SocketIO(app, cors_allowed_origins="*")
@app.route('/')
def index():
return render_template('index.html')
@socketio.on('connect')
def handle_connect():
print(f'Client connected: {request.sid}')
emit('my response', {'data': 'Welcome to Flask WebSocket chat!'})
@socketio.on('disconnect')
def handle_disconnect():
print(f'Client disconnected: {request.sid}')
@socketio.on('my event')
def handle_message(data):
try:
message = data.get('message', '').strip()
if not message:
emit('my response', {'data': 'Message cannot be empty'})
return
print(f'Received message: {message}')
# Echo the message back to the sender
emit('my response', {'data': f'Echo: {message}'})
except Exception as e:
print(f'Error processing message: {e}')
emit('my response', {'data': 'Error processing your message'})
if __name__ == '__main__':
socketio.run(app, debug=True, host='127.0.0.1', port=5000)
Flask-SocketIO decorators register functions to handle specific events. When clients connect, the connect
handler fires and sends a welcome message using emit()
. The custom event handler my event
processes incoming messages with input validation and error handling. Each client gets a unique session ID accessible via request.sid
.
The emit()
function sends responses back to clients, while the event-driven pattern keeps different message types organized in separate handlers.
Restart your server to activate the new event handlers:
python app.py
Refresh http://127.0.0.1:5000
and observe the welcome message appearing automatically:
The green connection status and automatic welcome message confirm your WebSocket event system is functioning correctly.
Test the echo functionality by typing a message and pressing Enter:
Messages return with an "Echo:" prefix, proving your Flask application successfully processes WebSocket events bidirectionally:
Your real-time messaging foundation is complete!
Broadcasting messages to multiple clients
Single-user echo systems only scratch the surface of WebSocket potential. Most real-time applications shine when they connect multiple users simultaneously—think group chats, collaborative tools, or live updates that reach everyone at once.
Transform your echo server into a multi-user broadcasting system:
from flask import Flask, render_template, request
from flask_socketio import SocketIO, emit
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key-here'
socketio = SocketIO(app, cors_allowed_origins="*")
# Track active connections
active_connections = set()
@app.route('/')
def index():
return render_template('index.html')
@socketio.on('connect')
def handle_connect():
# Add client to active connections
active_connections.add(request.sid)
print(f'Client connected: {request.sid}')
print(f'Total connections: {len(active_connections)}')
# Send welcome message to the connecting client
emit('my response', {
'data': f'Welcome! {len(active_connections)} clients connected.',
'sender': 'system'
})
# Notify all other clients about the new user
emit('my response', {
'data': f'New user joined. {len(active_connections)} total users.',
'sender': 'system'
}, broadcast=True, include_self=False)
@socketio.on('disconnect')
def handle_disconnect():
# Remove client from active connections
active_connections.discard(request.sid)
print(f'Client disconnected: {request.sid}')
print(f'Remaining connections: {len(active_connections)}')
# Notify remaining clients about user leaving
emit('my response', {
'data': f'User left. {len(active_connections)} total users.',
'sender': 'system'
}, broadcast=True)
@socketio.on('my event')
def handle_message(data):
try:
message = data.get('message', '').strip()
if not message:
emit('my response', {
'data': 'Message cannot be empty',
'sender': 'system'
})
return
if len(message) > 500:
emit('my response', {
'data': 'Message too long (max 500 characters)',
'sender': 'system'
})
return
print(f'Broadcasting message from {request.sid}: {message}')
# Broadcast message to all other clients
emit('my response', {
'data': f'User says: {message}',
'sender': 'other'
}, broadcast=True, include_self=False)
except Exception as e:
print(f'Error processing message: {e}')
emit('my response', {
'data': 'Error processing your message',
'sender': 'system'
})
if __name__ == '__main__':
socketio.run(app, debug=True, host='127.0.0.1', port=5000)
The connection tracking system uses a Python set to maintain active session IDs efficiently. Broadcasting with broadcast=True
sends messages to all connected clients, while include_self=False
prevents senders from receiving their own messages. This creates natural conversation flow where users see others' messages but not duplicates of their own.
Enhance the client interface to distinguish between message types:
<!-- Update just the JavaScript section in your existing HTML -->
<script src="https://cdn.socket.io/4.8.1/socket.io.min.js"></script>
<script>
const socket = io();
const messages = document.getElementById('messages');
const messageInput = document.getElementById('messageInput');
const sendButton = document.getElementById('sendButton');
const status = document.getElementById('status');
socket.on('connect', function() {
status.textContent = 'Connected to chat server';
status.classList.add('connected');
console.log('Connected to server');
});
socket.on('connect_error', function(error) {
status.textContent = 'Connection failed';
status.classList.remove('connected');
console.log('Connection error:', error);
});
socket.on('disconnect', function(reason) {
status.textContent = 'Disconnected from chat server';
status.classList.remove('connected');
console.log('Disconnected from server:', reason);
});
socket.on('my response', function(data) {
addMessage(data.data, data.sender);
});
function addMessage(message, sender) {
const messageElement = document.createElement('div');
messageElement.classList.add('message');
messageElement.textContent = message;
if (sender === 'self') {
messageElement.classList.add('own');
} else if (sender === 'system') {
messageElement.classList.add('system');
} else if (sender === 'other') {
// Messages from other users - default styling
messageElement.style.backgroundColor = '#f3f4f6';
}
messages.appendChild(messageElement);
messages.scrollTop = messages.scrollHeight;
}
function sendMessage() {
const message = messageInput.value.trim();
if (message) {
socket.emit('my event', {message: message});
messageInput.value = '';
}
}
sendButton.addEventListener('click', sendMessage);
messageInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') {
sendMessage();
}
});
</script>
The updated client code examines the data.sender
field to apply different styling. System messages (sender === 'system'
) get the .system
class with yellow background and italic text, while other users' messages (sender === 'other'
) receive a light gray background (#f3f4f6
).
The addMessage()
function checks the sender value and applies appropriate CSS classes or inline styles, creating visual distinction between your messages, others' messages, and system notifications.
Restart your Flask server:
python app.py
Open multiple browser tabs to http://localhost:5000
and watch the connection count increase dynamically:
Type messages in different tabs to see real-time broadcasting in action:
Messages from a single client immediately show up in all other connected sessions, enabling a smooth group chat experience. You've successfully developed a real-time multi-user app using Flask WebSockets!
Final thoughts
This tutorial guided you through building real-time Flask applications using Socket.IO, progressing from simple WebSocket connections to advanced multi-client broadcasting systems with proper connection management.
For production deployments, explore user authentication integration, message persistence with databases, and scaling options using Redis or RabbitMQ message brokers. The Flask-SocketIO documentation offers comprehensive coverage of advanced features, while Flask's production deployment guide explains server configuration options.
This foundation enables you to build sophisticated real-time features, private rooms, file sharing, live notifications, collaborative editing, and more, making Flask-SocketIO a powerful addition to your web development toolkit.