Python Flask Real-Time Chat With Socketio

Written by

in

Looking to add a live, interactive chat feature to your web app without the hassle of complex JavaScript frameworks? Python Flask real-time chat with SocketIO gives you a lightweight, scalable solution that works across browsers and mobile devices. In this guide we’ll walk through everything you need to build a fully functional chat application—from setting up the Flask server and integrating SocketIO, to handling rooms, user authentication, and deploying to production. By the end, you’ll have a ready‑to‑use codebase and a solid understanding of how real‑time communication works under the hood.

Why Choose Flask and SocketIO for Real‑Time Chat?

  • Flask’s simplicity: A micro‑framework that lets you focus on business logic without boilerplate.
  • SocketIO compatibility: Provides a WebSocket‑like API that automatically falls back to long‑polling when necessary, ensuring reliable delivery.
  • Python ecosystem: Leverage familiar libraries (e.g., Flask‑Login, SQLAlchemy) for authentication and persistence.
  • Scalable architecture: Works seamlessly with message brokers like Redis or RabbitMQ for multi‑worker deployments.

Project Structure Overview

my_chat_app/
├─ app.py                 # Flask entry point
├─ requirements.txt       # Python dependencies
├─ templates/
│   └─ index.html         # Main chat UI
├─ static/
│   ├─ css/
│   │   └─ style.css
│   └─ js/
│       └─ chat.js
└─ models.py              # (optional) DB models for users, messages

Step‑by‑Step Implementation

1. Install Required Packages

Open your terminal and create a virtual environment. Then install Flask, Flask‑SocketIO, and a message broker client (Redis is a popular choice).

python -m venv venv
source venv/bin/activate   # Windows: venv\Scripts\activate
pip install Flask Flask‑SocketIO python‑engineio[gevent] redis

2. Create the Flask Application (app.py)

The core of our chat lives in app.py. Below is a minimal but production‑ready setup.

from flask import Flask, render_template, request, session, redirect, url_for
from flask_socketio import SocketIO, emit, join_room, leave_room
import os

app = Flask(__name__)
app.config['SECRET_KEY'] = os.getenv('SECRET_KEY', 'dev_secret')
socketio = SocketIO(app, cors_allowed_origins="*")  # Enable CORS for simplicity

# -------------------- Routes --------------------
@app.route('/', methods=['GET', 'POST'])
def index():
    if request.method == 'POST':
        username = request.form.get('username')
        if username:
            session['username'] = username
            return redirect(url_for('chat'))
    return render_template('index.html')

@app.route('/chat')
def chat():
    if 'username' not in session:
        return redirect(url_for('index'))
    return render_template('chat.html', username=session['username'])

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

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

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

if __name__ == '__main__':
    # For production, use a proper WSGI server (e.g., gunicorn) and a message queue.
    socketio.run(app, debug=True)

3. Build the Front‑End (templates/chat.html)

The HTML page loads SocketIO’s client library, connects to the server, and handles UI updates.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Flask Real‑Time Chat</title>
    <link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
    <script src="https://cdn.socket.io/4.5.4/socket.io.min.js"></script>
</head>
<body>
    <div class="chat-container">
        <h2>Welcome, {{ username }}!</h2>
        <div id="room-select">
            <input type="text" id="room-input" placeholder="Enter room name">
            <button id="join-btn">Join</button>
        </div>
        <div id="chat-box" class="hidden">
            <ul id="messages"></ul>
            <input id="msg-input" autocomplete="off" placeholder="Type a message..." />
            <button id="send-btn">Send</button>
            <button id="leave-btn">Leave</button>
        </div>
    </div>

    <script src="{{ url_for('static', filename='js/chat.js') }}"></script>
</body>
</html>

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

The script manages socket events, UI toggles, and message rendering.

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

    const joinBtn   = document.getElementById('join-btn');
    const leaveBtn  = document.getElementById('leave-btn');
    const sendBtn   = document.getElementById('send-btn');
    const roomInput = document.getElementById('room-input');
    const msgInput  = document.getElementById('msg-input');
    const chatBox   = document.getElementById('chat-box');
    const messages  = document.getElementById('messages');

    let currentRoom = null;

    // ---------- Helper ----------
    const addMessage = (text, type='msg') => {
        const li = document.createElement('li');
        li.className = type;
        li.textContent = text;
        messages.appendChild(li);
        messages.scrollTop = messages.scrollHeight;
    };

    // ---------- Join ----------
    joinBtn.onclick = () => {
        const room = roomInput.value.trim();
        if (!room) return;
        socket.emit('join', {room});
        currentRoom = room;
        chatBox.classList.remove('hidden');
        addMessage(`You joined "${room}"`, 'status');
    };

    // ---------- Leave ----------
    leaveBtn.onclick = () => {
        if (!currentRoom) return;
        socket.emit('leave', {room: currentRoom});
        addMessage(`You left "${currentRoom}"`, 'status');
        chatBox.classList.add('hidden');
        currentRoom = null;
    };

    // ---------- Send ----------
    sendBtn.onclick = () => {
        const msg = msgInput.value.trim();
        if (!msg || !currentRoom) return;
        socket.emit('message', {room: currentRoom, msg});
        msgInput.value = '';
    };

    // ---------- Receive ----------
    socket.on('message', data => {
        addMessage(`${data.user}: ${data.msg}`);
    });

    socket.on('status', data => {
        addMessage(data.msg, 'status');
    });
});

5. Styling the Chat (static/css/style.css)

A clean UI improves user retention. Below is a minimal CSS snippet that keeps the focus on the conversation.

body {
    font-family: Arial, sans-serif;
    background: #f4f7f9;
    margin: 0;
    padding: 20px;
}
.chat-container {
    max-width: 600px;
    margin: auto;
    background: #fff;
    border-radius: 8px;
    padding: 20px;
    box-shadow: 0 2px 8px rgba(0,0,0,.1);
}
#messages {
    list-style: none;
    padding: 0;
    max-height: 300px;
    overflow-y: auto;
    margin-bottom: 10px;
}
#messages li {
    padding: 5px 10px;
    border-radius: 4px;
}
#messages li.msg { background: #e1f5fe; }
#messages li.status { color: #777; font-style: italic; }
.hidden { display: none; }

Advanced Features You Can Add

  • Persisted chat history: Store messages in a database (e.g., PostgreSQL) and load recent logs when a user joins a room.
  • Private messaging: Create one‑to‑one rooms using unique identifiers and emit events only to those sockets.
  • Authentication middleware: Use Flask‑Login to protect the chat routes and attach user IDs to socket sessions.
  • Typing indicators: Broadcast a typing event when a user is composing a message.
  • Scalable deployment: Pair Flask‑SocketIO with eventlet or gevent workers and a Redis message queue to synchronize multiple processes.

Testing Your Real‑Time Chat Locally

  1. Run the Flask server: python app.py.

Comments

Leave a Reply

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