Python Flask Real-Time Chat With Socketio

Written by

in

Building a real‑time chat application is one of the most exciting ways to showcase the power of Python Flask combined with SocketIO. Whether you’re a seasoned developer looking to add live messaging to a web app or a beginner eager to see Flask in action, this guide walks you through every step—from setting up the environment to deploying a production‑ready chat server. By the end, you’ll have a fully functional, responsive chat room that updates instantly for every connected user, all while keeping your code clean, maintainable, and SEO‑friendly.

Why Choose Flask and SocketIO for Real‑Time Chat?

Flask is a lightweight, micro‑framework that gives you the flexibility to structure your project exactly the way you want. When paired with SocketIO, you gain a powerful, event‑driven communication layer that abstracts the complexities of WebSockets, long‑polling, and fallback transports. This combination offers:

  • Fast development cycles—Flask’s simple routing and templating let you prototype quickly.
  • Bi‑directional communication—SocketIO enables instant message delivery from server to client and vice‑versa.
  • Scalability—Both Flask and SocketIO can be scaled horizontally with message brokers like Redis.
  • Cross‑browser support—SocketIO automatically selects the best transport method for each client.

Prerequisites and Environment Setup

System Requirements

Before you dive into code, make sure you have the following installed:

  1. Python 3.8 or newer
  2. pip (Python package manager)
  3. Virtualenv (optional but recommended)

Installing Required Packages

Open a terminal and run the commands below. They install Flask, Flask‑SocketIO, and a development server based on eventlet (or gevent if you prefer).

python -m venv venv
source venv/bin/activate   # On Windows use `venv\Scripts\activate`
pip install Flask Flask-SocketIO eventlet

These packages provide the core functionality you’ll need to handle HTTP routes, WebSocket connections, and asynchronous event loops.

Creating the Flask Application

Project Structure

A clean project layout makes maintenance easier. Below is a minimal yet organized structure:

my_chat_app/
│
├─ app.py               # Main Flask & SocketIO entry point
├─ templates/
│   └─ index.html       # Chat UI
└─ static/
    └─ js/
        └─ chat.js      # Client‑side SocketIO logic

Writing app.py

The following snippet sets up a Flask app, initializes SocketIO, and defines the essential events for joining a room and sending messages.

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

app = Flask(__name__)
app.config['SECRET_KEY'] = 'replace-with-a-secure-key'
socketio = SocketIO(app, async_mode='eventlet')

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

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

@socketio.on('message')
def handle_message(data):
    room = data['room']
    emit('message', data, room=room)

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

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

Key points to note:

  • async_mode='eventlet' tells Flask‑SocketIO to use the highly efficient Eventlet server.
  • Each event (join, message, leave) broadcasts to the specified room, keeping conversations isolated.
  • The SECRET_KEY should be replaced with a strong, random value in production.

Designing the Front‑End Chat Interface

HTML Template (templates/index.html)

This template provides a simple UI with a username field, a room selector, and a message input. It also loads the SocketIO client library from a CDN.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Flask SocketIO Real‑Time Chat</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/milligram/1.4.1/milligram.min.css">
    <style>
        #chat { height: 400px; overflow-y: scroll; border: 1px solid #ddd; padding: 10px; }
        .status { color: #777; font-style: italic; }
    </style>
</head>
<body>
    <div class="container">
        <h2>Flask‑SocketIO Real‑Time Chat</h2>
        <div id="login">
            <input type="text" id="username" placeholder="Enter your name">
            <input type="text" id="room" placeholder="Room name">
            <button id="joinBtn">Join Chat</button>
        </div>
        <div id="chatSection" style="display:none;">
            <div id="chat"></div>
            <input type="text" id="msgInput" placeholder="Type a message">
            <button id="sendBtn">Send</button>
            <button id="leaveBtn">Leave</button>
        </div>
    </div>
    <script src="https://cdn.socket.io/4.7.2/socket.io.min.js"></script>
    <script src="{{ url_for('static', filename='js/chat.js') }}"></script>
</body>
</html>

Client‑Side JavaScript (static/js/chat.js)

The script handles UI interactions, emits events to the server, and updates the chat window when new messages arrive.

document.addEventListener('DOMContentLoaded', () => {
    const socket = io();

    const loginDiv   = document.getElementById('login');
    const chatDiv    = document.getElementById('chatSection');
    const chatBox    = document.getElementById('chat');
    const usernameEl = document.getElementById('username');
    const roomEl     = document.getElementById('room');
    const msgInput   = document.getElementById('msgInput');

    // Join a chat room
    document.getElementById('joinBtn').onclick = () => {
        const username = usernameEl.value.trim();
        const room = roomEl.value.trim();
        if (!username || !room) return alert('Both fields are required.');
        socket.emit('join', {username, room});
        loginDiv.style.display = 'none';
        chatDiv.style.display = 'block';
    };

    // Send a message
    document.getElementById('sendBtn').onclick = () => {
        const msg = msgInput.value.trim();
        const room = roomEl.value.trim();
        const username = usernameEl.value.trim();
        if (msg) {
            socket.emit('message', {username, room, msg});
            msgInput.value = '';
        }
    };

    // Leave the room
    document.getElementById('leaveBtn').onclick = () => {
        const username = usernameEl.value.trim();
        const room = roomEl.value.trim();
        socket.emit('leave', {username, room});
        chatDiv.style.display = 'none';
        loginDiv.style.display = 'block';
        chatBox.innerHTML = '';
    };

    // Receive a normal message
    socket.on('message', data => {
        const el = document.createElement('p');
        el.innerHTML = `${data.username}: ${data.msg}`;
        chatBox.appendChild(el);
        chatBox.scrollTop = chatBox.scrollHeight;
    });

    // Receive status updates (join/leave)
    socket.on('status', data => {
        const el = document.createElement('p');
        el.className = 'status';
        el.textContent = data.msg;
        chatBox.appendChild(el);
        chatBox.scrollTop = chatBox.scrollHeight;
    });
});

Adding Advanced Features

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *