Flask Chat Application
Real-time communication has become a fundamental feature of modern web applications. In this tutorial, we'll build a simple yet functional chat application using Flask and WebSocket technology. This project will demonstrate how to implement bidirectional communication between a server and multiple clients, allowing users to exchange messages instantly.
Understanding WebSockets for Chat Applications
Traditional HTTP requests work in a request-response cycle, where the client must always initiate communication. This model isn't ideal for chat applications that require instant message delivery. WebSockets solve this problem by establishing a persistent connection that allows both the server and client to send messages at any time.
For our Flask chat application, we'll use Flask-SocketIO, an extension that integrates Socket.IO with Flask, making WebSocket implementation straightforward.
Prerequisites
Before starting, make sure you have:
- Basic knowledge of Flask
- Python 3.6 or higher installed
- Familiarity with HTML, CSS, and JavaScript
Setting Up the Project
Let's begin by creating our project structure:
flask-chat-app/
├── static/
│ └── style.css
│ └── script.js
├── templates/
│ └── index.html
├── app.py
└── requirements.txt
First, let's install the required packages:
pip install flask flask-socketio
Create a requirements.txt
file to track dependencies:
flask==2.2.3
flask-socketio==5.3.3
python-engineio==4.4.1
python-socketio==5.8.0
Building the Flask WebSocket Server
Let's create our app.py
file that will serve as our Flask server:
from flask import Flask, render_template
from flask_socketio import SocketIO, send, emit, join_room, leave_room
app = Flask(__name__)
app.config['SECRET_KEY'] = 'your-secret-key'
socketio = SocketIO(app, cors_allowed_origins="*")
# Store active users
users = {}
@app.route('/')
def index():
return render_template('index.html')
@socketio.on('connect')
def handle_connect():
print(f'Client connected: {request.sid}')
@socketio.on('disconnect')
def handle_disconnect():
for username, sid in list(users.items()):
if sid == request.sid:
users.pop(username)
emit('user_left', {'username': username}, broadcast=True)
print(f'User {username} disconnected')
break
print(f'Client disconnected: {request.sid}')
@socketio.on('join')
def handle_join(data):
username = data['username']
users[username] = request.sid
emit('user_joined', {'username': username}, broadcast=True)
emit('user_list', list(users.keys()), broadcast=True)
print(f'User {username} joined')
@socketio.on('message')
def handle_message(data):
print(f"Message: {data}")
emit('message', data, broadcast=True)
if __name__ == '__main__':
socketio.run(app, debug=True)
This script sets up a Flask application with Socket.IO integration. It defines event handlers for connection, disconnection, joining the chat, and sending messages.
Creating the Frontend
Now let's create our HTML template. Create index.html
inside the templates folder:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Flask Chat App</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
<div class="chat-container">
<div class="chat-header">
<h1>Flask Chat App</h1>
</div>
<div id="join-container" class="join-container">
<h2>Join the Chat</h2>
<input type="text" id="username-input" placeholder="Your username...">
<button id="join-btn">Join</button>
</div>
<div id="chat-box-container" class="chat-box-container hidden">
<div class="user-list-container">
<h3>Online Users</h3>
<ul id="user-list"></ul>
</div>
<div class="messages-container">
<div id="messages" class="messages"></div>
<div class="message-input-container">
<input type="text" id="message-input" placeholder="Type a message...">
<button id="send-btn">Send</button>
</div>
</div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.6.1/socket.io.js"></script>
<script src="{{ url_for('static', filename='script.js') }}"></script>
</body>
</html>
Now let's add some CSS styling in static/style.css
:
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: Arial, sans-serif;
}
.chat-container {
max-width: 900px;
margin: 0 auto;
padding: 20px;
}
.chat-header {
text-align: center;
margin-bottom: 20px;
}
.join-container {
text-align: center;
padding: 20px;
border: 1px solid #ccc;
border-radius: 5px;
}
.join-container h2 {
margin-bottom: 15px;
}
#username-input {
padding: 10px;
width: 200px;
margin-right: 10px;
}
.chat-box-container {
display: flex;
height: 500px;
border: 1px solid #ccc;
border-radius: 5px;
}
.user-list-container {
width: 25%;
padding: 15px;
border-right: 1px solid #ccc;
}
.user-list-container h3 {
margin-bottom: 10px;
}
#user-list {
list-style-type: none;
}
#user-list li {
padding: 8px 0;
border-bottom: 1px solid #eee;
}
.messages-container {
width: 75%;
display: flex;
flex-direction: column;
}
.messages {
flex-grow: 1;
padding: 15px;
overflow-y: auto;
}
.message-input-container {
display: flex;
padding: 10px;
border-top: 1px solid #ccc;
}
#message-input {
flex-grow: 1;
padding: 10px;
}
button {
padding: 10px 15px;
background-color: #007bff;
color: white;
border: none;
cursor: pointer;
}
button:hover {
background-color: #0056b3;
}
.hidden {
display: none;
}
.message {
margin-bottom: 10px;
padding: 8px;
border-radius: 5px;
max-width: 80%;
}
.message.received {
background-color: #f1f0f0;
align-self: flex-start;
}
.message.sent {
background-color: #d1e7ff;
align-self: flex-end;
margin-left: auto;
}
.system-message {
text-align: center;
color: #888;
margin: 10px 0;
font-style: italic;
}
Finally, let's create our JavaScript for the client side in static/script.js
:
document.addEventListener('DOMContentLoaded', () => {
const socket = io();
// DOM elements
const joinContainer = document.getElementById('join-container');
const chatBoxContainer = document.getElementById('chat-box-container');
const usernameInput = document.getElementById('username-input');
const joinBtn = document.getElementById('join-btn');
const messageInput = document.getElementById('message-input');
const sendBtn = document.getElementById('send-btn');
const messages = document.getElementById('messages');
const userList = document.getElementById('user-list');
let username = '';
// Socket connection events
socket.on('connect', () => {
console.log('Connected to server');
});
socket.on('disconnect', () => {
console.log('Disconnected from server');
});
// Join chat event
joinBtn.addEventListener('click', () => {
username = usernameInput.value.trim();
if (username) {
socket.emit('join', { username });
joinContainer.classList.add('hidden');
chatBoxContainer.classList.remove('hidden');
messageInput.focus();
}
});
// Allow enter key to join
usernameInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
joinBtn.click();
}
});
// Send message event
sendBtn.addEventListener('click', sendMessage);
// Allow enter key to send message
messageInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
sendMessage();
}
});
function sendMessage() {
const message = messageInput.value.trim();
if (message) {
socket.emit('message', {
username,
message,
time: new Date().toLocaleTimeString()
});
messageInput.value = '';
}
}
// Handle received message
socket.on('message', (data) => {
const messageElement = document.createElement('div');
messageElement.classList.add('message');
if (data.username === username) {
messageElement.classList.add('sent');
messageElement.innerHTML = `
<div>${data.message}</div>
<small>${data.time}</small>
`;
} else {
messageElement.classList.add('received');
messageElement.innerHTML = `
<strong>${data.username}</strong>
<div>${data.message}</div>
<small>${data.time}</small>
`;
}
messages.appendChild(messageElement);
// Scroll to the bottom of the messages
messages.scrollTop = messages.scrollHeight;
});
// Handle user join notification
socket.on('user_joined', (data) => {
const systemMessage = document.createElement('div');
systemMessage.classList.add('system-message');
systemMessage.textContent = `${data.username} has joined the chat`;
messages.appendChild(systemMessage);
// Scroll to the bottom of the messages
messages.scrollTop = messages.scrollHeight;
});
// Handle user left notification
socket.on('user_left', (data) => {
const systemMessage = document.createElement('div');
systemMessage.classList.add('system-message');
systemMessage.textContent = `${data.username} has left the chat`;
messages.appendChild(systemMessage);
// Scroll to the bottom of the messages
messages.scrollTop = messages.scrollHeight;
});
// Update user list
socket.on('user_list', (users) => {
userList.innerHTML = '';
users.forEach(user => {
const userElement = document.createElement('li');
userElement.textContent = user;
if (user === username) {
userElement.textContent += ' (You)';
userElement.style.fontWeight = 'bold';
}
userList.appendChild(userElement);
});
});
});
Running and Testing the Application
Now let's run our application. From your project directory, execute:
python app.py
Navigate to http://localhost:5000
in your web browser. You should see the login page. Open the application in multiple browser tabs or windows to simulate different users, and start sending messages to test the real-time functionality.
How It Works
Let's break down the key components of our chat application:
-
WebSocket Connection: Socket.IO establishes a persistent WebSocket connection between the client and server.
-
User Management:
- Users join by providing a username
- The server keeps track of active users
- When users join or leave, all clients are notified
-
Message Broadcasting:
- When a client sends a message, it's received by the server
- The server then broadcasts the message to all connected clients
- Messages include sender information and timestamps
-
Real-time Updates:
- User list updates in real-time
- Join/leave notifications are sent to all users
- Messages appear instantly for all participants
Enhancing the Application
Here are some ways you could enhance this basic chat application:
- Private Messaging: Implement functionality for users to send private messages to specific users.
@socketio.on('private_message')
def handle_private_message(data):
recipient = data['recipient']
recipient_sid = users.get(recipient)
if recipient_sid:
emit('private_message', data, room=recipient_sid)
emit('private_message', data, room=request.sid) # Send to sender as well
- Chat Rooms: Allow users to create and join different chat rooms.
@socketio.on('join_room')
def handle_join_room(data):
room = data['room']
join_room(room)
emit('room_message', {'msg': f"{data['username']} has joined the room {room}"}, room=room)
-
Message Persistence: Store messages in a database to provide chat history.
-
User Authentication: Add proper user authentication instead of just using usernames.
-
Message Types: Support different message types like images, files, or emojis.
Common Issues and Troubleshooting
-
CORS Errors: If you're getting CORS errors, make sure you've configured
cors_allowed_origins
correctly in your Flask-SocketIO initialization. -
Connection Issues: If clients are having trouble connecting, check if any proxy or firewall is blocking WebSocket connections.
-
High Latency: If messages are delayed, consider optimizing your server setup or implementing a load balancer.
Summary
In this tutorial, we've built a functional real-time chat application using Flask and WebSockets. We've implemented:
- A Flask server with Socket.IO integration
- Real-time bidirectional communication
- User management and notifications
- Message broadcasting and display
This project demonstrates the power of WebSockets for real-time applications. Chat applications are just one example - the same principles can be applied to build collaborative tools, live dashboards, gaming applications, and more.
Further Exercises
- Implement a feature that shows when users are typing
- Add support for emoji reactions to messages
- Create a "read receipts" feature that shows when messages have been seen
- Add message deletion functionality
- Implement message search capability
- Add user profiles with avatars
Additional Resources
- Flask Documentation
- Flask-SocketIO Documentation
- Socket.IO JavaScript Client
- WebSockets MDN Web Docs
- Designing Chat Applications
By following this tutorial, you've gained valuable experience with WebSockets in Flask and learned how to implement real-time features in your web applications. This knowledge will be useful for many different types of projects that require instant updates and bidirectional communication.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)