Skip to main content

Flask Rooms and Namespaces

In real-time applications built with Flask-SocketIO, managing communication channels efficiently becomes crucial as your application grows. This is where rooms and namespaces come in - they help you organize your WebSocket connections and control the flow of messages between clients and the server.

Introduction​

When building WebSocket applications with Flask, you'll often need ways to:

  • Group users together (like in a chat room)
  • Separate different parts of your application (like messaging vs notifications)
  • Send messages to specific groups of users

Flask-SocketIO provides two powerful concepts to handle these scenarios:

  1. Rooms: Virtual groupings of connected clients
  2. Namespaces: Separate communication channels within your application

Let's explore both in detail.

Understanding SocketIO Namespaces​

Namespaces are distinct communication channels that let you partition your application logic. Think of them as different "sections" of your WebSocket application.

Why Use Namespaces?​

  • Organization: Separate different features (chat, notifications, status updates)
  • Security: Apply different authentication rules to different parts of your app
  • Efficiency: Reduce unnecessary message processing

Basic Namespace Implementation​

Here's how to set up namespaces in your Flask-SocketIO application:

python
from flask import Flask, render_template
from flask_socketio import SocketIO, emit, namespace

app = Flask(__name__)
socketio = SocketIO(app)

# Default namespace (implicitly '/')
@socketio.on('message')
def handle_message(data):
print('received message: ' + data)
emit('response', {'data': 'Default namespace response'})

# Chat namespace
@socketio.on('message', namespace='/chat')
def handle_chat_message(data):
print('received chat message: ' + data)
emit('response', {'data': 'Chat namespace response'})

# Notification namespace
@socketio.on('message', namespace='/notifications')
def handle_notification(data):
print('received notification: ' + data)
emit('response', {'data': 'Notification namespace response'})

if __name__ == '__main__':
socketio.run(app, debug=True)

Connecting to Specific Namespaces (Client Side)​

On the client side, you need to specify which namespace you're connecting to:

javascript
// Connect to default namespace
const defaultSocket = io();

// Connect to chat namespace
const chatSocket = io('/chat');

// Connect to notifications namespace
const notificationSocket = io('/notifications');

// Sending messages to different namespaces
defaultSocket.emit('message', 'Hello default namespace');
chatSocket.emit('message', 'Hello chat namespace');
notificationSocket.emit('message', 'Hello notification namespace');

// Listening for responses
defaultSocket.on('response', (data) => {
console.log('Default response:', data.data);
});

chatSocket.on('response', (data) => {
console.log('Chat response:', data.data);
});

notificationSocket.on('response', (data) => {
console.log('Notification response:', data.data);
});

Working with Rooms​

While namespaces partition your application vertically, rooms allow you to create dynamic groups of clients within a namespace. This is perfect for features like chat rooms, private messaging, or broadcasts to specific user groups.

Why Use Rooms?​

  • Send messages to specific groups of users
  • Implement private chats or multi-user conversations
  • Create dynamic user groupings (e.g., users viewing the same product)

Room Basics​

Every client connection in Flask-SocketIO is automatically assigned a unique room (with the name being the session ID). This means you can send a message to just one client using their session ID as the room.

Here's a simple implementation of rooms:

python
from flask import Flask, request
from flask_socketio import SocketIO, join_room, leave_room, emit

app = Flask(__name__)
socketio = SocketIO(app)

@socketio.on('join')
def on_join(data):
username = data['username']
room = data['room']
join_room(room)
emit('status', {'msg': f'{username} has entered the room.'}, room=room)

@socketio.on('leave')
def on_leave(data):
username = data['username']
room = data['room']
leave_room(room)
emit('status', {'msg': f'{username} has left the room.'}, room=room)

@socketio.on('message')
def handle_message(data):
room = data['room']
emit('message', {'user': data['username'], 'msg': data['msg']}, room=room)

if __name__ == '__main__':
socketio.run(app, debug=True)

Client-Side Room Implementation​

javascript
const socket = io();
const username = 'User123';
let currentRoom = '';

// Function to join a room
function joinRoom(room) {
// Leave current room first if already in one
if (currentRoom) {
socket.emit('leave', {username: username, room: currentRoom});
}

// Join new room
currentRoom = room;
socket.emit('join', {username: username, room: room});
console.log(`Joined room: ${room}`);
}

// Send a message to current room
function sendMessage(message) {
socket.emit('message', {
username: username,
room: currentRoom,
msg: message
});
}

// Listen for messages
socket.on('message', function(data) {
console.log(`${data.user}: ${data.msg}`);
// Update UI with the message
addMessageToChat(data.user, data.msg);
});

// Listen for status updates (join/leave)
socket.on('status', function(data) {
console.log(data.msg);
// Update UI with status
addStatusToChat(data.msg);
});

Combining Namespaces and Rooms​

For larger applications, you can leverage both namespaces and rooms together. This gives you fine-grained control over message routing.

Real-World Example: Multi-Room Chat Application​

Here's how you might structure a chat application with public and private rooms:

python
from flask import Flask, render_template, request
from flask_socketio import SocketIO, emit, join_room, leave_room

app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key'
socketio = SocketIO(app)

# Store active users and their session IDs
users = {}

@app.route('/')
def index():
return render_template('index.html')

# Chat namespace handlers
@socketio.on('connect', namespace='/chat')
def chat_connect():
print('Client connected to chat namespace')

@socketio.on('register', namespace='/chat')
def register_user(data):
users[data['username']] = request.sid
emit('user_list', {'users': list(users.keys())}, broadcast=True, namespace='/chat')

@socketio.on('join_room', namespace='/chat')
def on_join_room(data):
username = data['username']
room = data['room']
join_room(room)
emit('room_message', {'msg': f'{username} has joined the room'}, room=room, namespace='/chat')

@socketio.on('leave_room', namespace='/chat')
def on_leave_room(data):
username = data['username']
room = data['room']
leave_room(room)
emit('room_message', {'msg': f'{username} has left the room'}, room=room, namespace='/chat')

@socketio.on('room_message', namespace='/chat')
def on_room_message(data):
room = data['room']
emit('room_message', {
'username': data['username'],
'message': data['message'],
'room': room
}, room=room, namespace='/chat')

# Private message handler
@socketio.on('private_message', namespace='/chat')
def on_private_message(data):
recipient = data['recipient']
if recipient in users:
recipient_session_id = users[recipient]
emit('private_message', {
'username': data['username'],
'message': data['message']
}, room=recipient_session_id, namespace='/chat')

# Also send to sender
emit('private_message', {
'username': data['username'],
'message': data['message'],
'recipient': recipient
}, room=request.sid, namespace='/chat')

# Notification namespace handlers
@socketio.on('connect', namespace='/notifications')
def notification_connect():
print('Client connected to notifications namespace')

@socketio.on('broadcast_notification', namespace='/notifications')
def broadcast_notification(data):
emit('notification', {'message': data['message']}, broadcast=True, namespace='/notifications')

if __name__ == '__main__':
socketio.run(app, debug=True)

Client-Side Implementation​

html
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
<title>Flask-SocketIO Chat Example</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script>
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', () => {
// Connect to namespaces
const chatSocket = io('/chat');
const notificationSocket = io('/notifications');

let username = '';
let currentRoom = '';

// Register user
document.getElementById('register-btn').addEventListener('click', () => {
username = document.getElementById('username').value;
if (username) {
chatSocket.emit('register', {username: username});
document.getElementById('registration').style.display = 'none';
document.getElementById('chat-container').style.display = 'block';
}
});

// Join room
document.getElementById('join-btn').addEventListener('click', () => {
const room = document.getElementById('room-input').value;
if (room) {
if (currentRoom) {
chatSocket.emit('leave_room', {username: username, room: currentRoom});
}
currentRoom = room;
chatSocket.emit('join_room', {username: username, room: room});
document.getElementById('current-room').textContent = room;
}
});

// Send room message
document.getElementById('send-room-btn').addEventListener('click', () => {
const message = document.getElementById('room-message').value;
if (message && currentRoom) {
chatSocket.emit('room_message', {
username: username,
message: message,
room: currentRoom
});
document.getElementById('room-message').value = '';
}
});

// Send private message
document.getElementById('send-private-btn').addEventListener('click', () => {
const recipient = document.getElementById('recipient').value;
const message = document.getElementById('private-message').value;
if (recipient && message) {
chatSocket.emit('private_message', {
username: username,
recipient: recipient,
message: message
});
document.getElementById('private-message').value = '';
}
});

// Listen for messages
chatSocket.on('room_message', (data) => {
const messagesDiv = document.getElementById('room-messages');
const messageElement = document.createElement('p');

if (data.username) {
messageElement.textContent = `${data.username}: ${data.message}`;
} else {
messageElement.textContent = data.msg;
messageElement.style.fontStyle = 'italic';
}

messagesDiv.appendChild(messageElement);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
});

chatSocket.on('private_message', (data) => {
const messagesDiv = document.getElementById('private-messages');
const messageElement = document.createElement('p');

if (data.recipient) {
messageElement.textContent = `You to ${data.recipient}: ${data.message}`;
} else {
messageElement.textContent = `${data.username} (private): ${data.message}`;
}

messagesDiv.appendChild(messageElement);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
});

chatSocket.on('user_list', (data) => {
const userList = document.getElementById('user-list');
userList.innerHTML = '';
data.users.forEach(user => {
if (user !== username) {
const option = document.createElement('option');
option.value = user;
option.textContent = user;
userList.appendChild(option);
}
});
});

// Notification system
document.getElementById('send-notification-btn').addEventListener('click', () => {
const notification = document.getElementById('notification-text').value;
if (notification) {
notificationSocket.emit('broadcast_notification', {message: notification});
document.getElementById('notification-text').value = '';
}
});

notificationSocket.on('notification', (data) => {
const notificationsDiv = document.getElementById('notifications');
const notificationElement = document.createElement('p');
notificationElement.textContent = `Notification: ${data.message}`;
notificationsDiv.appendChild(notificationElement);

// Show temporary alert
const alert = document.createElement('div');
alert.className = 'notification-alert';
alert.textContent = data.message;
document.body.appendChild(alert);

setTimeout(() => {
alert.style.opacity = '0';
setTimeout(() => {
document.body.removeChild(alert);
}, 500);
}, 3000);
});
});
</script>
<style>
.notification-alert {
position: fixed;
top: 20px;
right: 20px;
background-color: #4CAF50;
color: white;
padding: 15px;
border-radius: 5px;
transition: opacity 0.5s;
}
</style>
</head>
<body>
<div id="registration">
<h2>Enter your username</h2>
<input type="text" id="username" placeholder="Username">
<button id="register-btn">Register</button>
</div>

<div id="chat-container" style="display: none;">
<div style="display: flex;">
<div style="flex: 1; margin-right: 10px;">
<h2>Room Chat</h2>
<div>
<input type="text" id="room-input" placeholder="Room name">
<button id="join-btn">Join Room</button>
<p>Current room: <span id="current-room">None</span></p>
</div>
<div id="room-messages" style="height: 300px; overflow-y: scroll; border: 1px solid #ccc;"></div>
<div>
<input type="text" id="room-message" placeholder="Message">
<button id="send-room-btn">Send</button>
</div>
</div>

<div style="flex: 1;">
<h2>Private Messages</h2>
<div>
<select id="user-list"></select>
<input type="text" id="recipient" placeholder="Recipient username">
</div>
<div id="private-messages" style="height: 300px; overflow-y: scroll; border: 1px solid #ccc;"></div>
<div>
<input type="text" id="private-message" placeholder="Private message">
<button id="send-private-btn">Send</button>
</div>
</div>
</div>

<div style="margin-top: 20px;">
<h2>Notifications</h2>
<div>
<input type="text" id="notification-text" placeholder="Broadcast notification">
<button id="send-notification-btn">Send Notification</button>
</div>
<div id="notifications" style="height: 150px; overflow-y: scroll; border: 1px solid #ccc;"></div>
</div>
</div>
</body>
</html>

Best Practices​

When working with rooms and namespaces, keep these tips in mind:

  1. Plan Your Structure: Before coding, map out your application's namespaces and room structure

  2. Room Naming: Use clear, consistent naming conventions for rooms

    • Chat rooms: chat_room_{id}
    • User-specific rooms: user_{id}
    • Broadcast groups: group_{type}
  3. Join/Leave Management: Always have users leave rooms when they disconnect or navigate away

  4. Authentication: Implement proper authentication before allowing users to join sensitive rooms

  5. Error Handling: Handle edge cases like trying to join non-existent rooms or sending to disconnected users

  6. Documentation: Document your namespace and room architecture for easier maintenance

Performance Considerations​

  • Memory Usage: Each room requires memory to track participants
  • Message Volume: Broadcasting to many rooms simultaneously can impact performance
  • Connection Management: Clean up rooms when users disconnect to prevent memory leaks

Summary​

Flask-SocketIO's rooms and namespaces provide powerful ways to structure your real-time applications:

  • Namespaces divide your application into logical sections
  • Rooms allow dynamic grouping of connections within namespaces
  • Combined, they give you fine-grained control over message routing

With these tools, you can build sophisticated real-time applications that efficiently manage communication between users and components of your system.

Additional Resources​

Exercises​

  1. Create a simple chat application with two rooms: "general" and "random"
  2. Extend the chat application to support private messaging between users
  3. Build a collaborative drawing application where users in the same room see real-time updates
  4. Implement a notification system that sends different types of alerts through different namespaces
  5. Create a real-time dashboard that updates specific components based on room subscriptions


If you spot any mistakes on this website, please let me know at feedback@compilenrun.com. I’d greatly appreciate your feedback! :)