Real-Time with WebSockets (Flask-SocketIO)

Reviewed & published by Brayan K

A WebSocket keeps a single connection open in both directions, so the server can push live updates to the browser the instant they happen instead of waiting to be asked.

Part of the free Flask course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

In this lesson you'll build an event dispatcher and a rooms-and-broadcast model in plain Python, then see the real Flask-SocketIO server and client code.

What You'll Learn in This Lesson

1 HTTP vs WebSocket & the Event Pattern

Plain HTTP is request-response: the client asks, the server answers, the connection closes. To get fresh data the client must ask again (polling). A WebSocket instead opens one long-lived, two-way channel, so the server can push data the moment something changes — perfect for chat, live scores, and dashboards.

Socket.IO models this as named events: you register a handler with @socketio.on("event") and the framework calls it whenever that event arrives. The runnable EventBus below is exactly that idea in miniature — register handlers, then emit events to fire them.

# A tiny event dispatcher: the core idea behind socketio.on / emit
class EventBus:
    def __init__(self):
        self._handlers = {}   # event name -> list of callbacks

    def on(self, event):
        # decorator that registers a handler, like @socketio.on('message')
        def register(fn):
            self._handlers.setdefault(event, []).append(fn)
            return fn
        return register

    def emit(self, event, data):
        # call every handler registered for this event
        for fn in self._handlers.get(event, []):
            fn(data)

bus = EventBus()
log = []

@bus.on("connect")
def greet(data):
    log.append(f"client {data['sid']} connected")

@bus.on("message")
def echo(data):
    log.append(f"got message: {data['text']}")

# The server "receives" events and dispatches them:
bus.emit("connect", {"sid": "abc123"})
bus.emit("message", {"text": "hello"})
for line in log:
    print(line)
# client abc123 connected
# got message: hello

# ✅ Expected output:
# client abc123 connected
# got message: hello

Emitting connect then message runs each registered handler in turn — the same flow Flask-SocketIO drives when a real client connects and sends a message.

2 Rooms and Broadcasting

Often you don't want to message every client — just those watching one game or chatting in one channel. A room is a named group of clients. The server can emit to a single room, or broadcast to everyone by leaving the room out.

The runnable model below gives each client an outbox. alice and bob join the sports room; carol does not. A score event sent to sports reaches only alice and bob, while a global ping reaches all three.

# Rooms and broadcast: deliver an event to a subset of clients
class Server:
    def __init__(self):
        self.clients = {}          # sid -> outbox (list of messages)
        self.rooms = {}            # room name -> set of sids

    def connect(self, sid):
        self.clients[sid] = []

    def join_room(self, sid, room):
        self.rooms.setdefault(room, set()).add(sid)

    def emit(self, event, data, room=None):
        # broadcast to one room, or to everyone if room is None
        targets = self.rooms.get(room, set()) if room else set(self.clients)
        for sid in targets:
            self.clients[sid].append((event, data))

s = Server()
for sid in ["alice", "bob", "carol"]:
    s.connect(sid)
s.join_room("alice", "sports")
s.join_room("bob", "sports")          # carol is NOT in 'sports'

s.emit("score", {"team": "Reds", "points": 3}, room="sports")
s.emit("ping", {"t": 1})              # to everyone

print("alice:", s.clients["alice"])   # alice gets score + ping
print("carol:", s.clients["carol"])   # carol gets only the ping

# ✅ Expected output:
# alice: [('score', {'team': 'Reds', 'points': 3}), ('ping', {'t': 1})]
# carol: [('ping', {'t': 1})]

alice's outbox holds both the score and the ping; carol's holds only the ping because she never joined sports. That is room-scoped delivery.

3 The Browser Client & Use Cases

A WebSocket has two sides. The browser uses the Socket.IO client library: it connects, emits events to the server, and listens with socket.on("event", handler) to receive pushes. The mental model is symmetric with the server's on/emit.

The runnable example simulates that client side — a small object that registers listeners and reacts when the "server" delivers an event — so you can see how a received push updates the UI.

# Simulating the browser client: socket.on(...) + receiving pushes
class Client:
    def __init__(self):
        self.listeners = {}     # event -> handler  (like socket.on)
        self.ui = []            # what the "page" shows

    def on(self, event, handler):
        self.listeners[event] = handler

    def receive(self, event, data):
        # the server pushed an event over the open connection
        if event in self.listeners:
            self.listeners[event](data)

client = Client()
client.on("score", lambda d: client.ui.append(f"SCORE: {d['team']} {d['points']}"))
client.on("chat",  lambda d: client.ui.append(f"chat: {d['msg']}"))

# Server pushes arrive without the client asking:
client.receive("score", {"team": "Reds", "points": 3})
client.receive("chat", {"msg": "great goal!"})
print(client.ui)   # ['SCORE: Reds 3', 'chat: great goal!']

# ✅ Expected output:
# ['SCORE: Reds 3', 'chat: great goal!']

🔬 WebSocket Cheat Sheet

You want to...Use
Handle an event@socketio.on("event")
Send to the senderemit("event", data)
Send to everyoneemit(..., broadcast=True)
Send to a roomemit(..., room="r")
Join a roomjoin_room("r")

🎯 Your Turn: Fill in the Blanks

Complete the bus below. Replace each ___ so handlers register and events dispatch.

# 🎯 YOUR TURN: replace each ___ to finish the event bus
class Bus:
    def __init__(self):
        self.handlers = {}
    def on(self, event):
        def reg(fn):
            self.handlers.setdefault(event, []).___(fn)   # store the handler
            return fn
        return reg
    def emit(self, event, data):
        for fn in self.handlers.get(event, []):
            fn(___)                                       # call it with the data

bus = Bus()
seen = []

@bus.on("chat")
def handle(data):
    seen.append(data["msg"])

bus.emit("chat", {"msg": "hi"})
bus.emit("chat", {"msg": "there"})
print(seen)   # ['hi', 'there']

Common Errors & Quick Reference

❌ Running socketio.run but events never arrive

You started the app with app.run() or a plain WSGI server. Flask-SocketIO needs socketio.run(app) (dev) or an async server like eventlet/gevent so it can hold the WebSocket open.

❌ Broadcast doesn't reach other tabs

By default emit goes only to the current client. Add broadcast=True for all clients, or room="name" to target a room they've joined.

TaskCode
Run the serversocketio.run(app)
Client connectconst socket = io()
Client listensocket.on("ev", fn)
Client sendsocket.emit("ev", data)

🏆 Mini Challenge: A Chat Room

Build a chat server that echoes each message to everyone except the sender.

# 🏆 Mini Challenge: a chat room that echoes to everyone except the sender
class ChatServer:
    def __init__(self):
        self.inboxes = {}   # sid -> received messages

    def connect(self, sid):
        self.inboxes[sid] = []

    def broadcast(self, sender, text):
        # send to all connected clients except the sender
        for sid in self.inboxes:
            if sid != sender:
                self.inboxes[sid].append(f"{sender}: {text}")

srv = ChatServer()
for sid in ["ada", "bob", "cy"]:
    srv.connect(sid)

srv.broadcast("ada", "hello everyone")
print("ada:", srv.inboxes["ada"])   # ada: []   (sender skipped)
print("bob:", srv.inboxes["bob"])   # bob: ['ada: hello everyone']
print("cy: ", srv.inboxes["cy"])    # cy:  ['ada: hello everyone']

❓ Frequently Asked Questions

Lesson complete — your app can talk back in real time!

You modeled the on/emit event pattern, scoped delivery with rooms, broadcast to everyone, simulated the browser client, and read the real Flask-SocketIO server and JavaScript client.

Practice quiz

How does a WebSocket differ from a normal HTTP request?

  • It is slower than HTTP
  • It keeps one long-lived two-way connection open
  • It only works for images
  • It cannot send data to the client

Answer: It keeps one long-lived two-way connection open. A WebSocket opens a single persistent, bidirectional channel so the server can push data anytime.

Which extension adds Socket.IO to a Flask app?

  • Flask-WS
  • Flask-Realtime
  • Flask-Push
  • Flask-SocketIO

Answer: Flask-SocketIO. Flask-SocketIO integrates the Socket.IO real-time protocol with Flask.

How do you register a server-side event handler?

  • @socketio.on('event')
  • @app.route('event')
  • @socketio.handle('event')
  • @socketio.listen('event')

Answer: @socketio.on('event'). @socketio.on('event') registers a handler that fires when that named event arrives.

Which function sends an event to a client?

  • send()
  • push()
  • emit()
  • dispatch()

Answer: emit(). emit('event', data) sends an event; by default in a handler it goes back to the sender.

How do you send an event to every connected client?

  • emit(..., broadcast=True)
  • emit(..., all=True)
  • emit(..., everyone=True)
  • broadcast_all(...)

Answer: emit(..., broadcast=True). Passing broadcast=True to emit sends the event to all connected clients.

What is a room in Socket.IO?

  • A separate server process
  • A database table
  • A named group of connected clients
  • A type of event

Answer: A named group of connected clients. A room is a named group of clients; you emit to a room to target just that subset.

How does a client join a room inside a handler?

  • add_room('name')
  • join_room('name')
  • room.join('name')
  • socketio.join('name')

Answer: join_room('name'). join_room('name') adds the current client to a room so it can receive room-targeted events.

How do you emit an event only to clients in a room?

  • emit(..., only='name')
  • emit(..., group='name')
  • emit(..., room='name')
  • emit(..., to_room='name')

Answer: emit(..., room='name'). emit('event', data, room='name') delivers the event only to clients that joined that room.

How does the browser client listen for a pushed event?

  • socket.listen('event', fn)
  • socket.receive('event', fn)
  • socket.handle('event', fn)
  • socket.on('event', fn)

Answer: socket.on('event', fn). On the client, socket.on('event', handler) reacts to events the server pushes.

Why can't a plain WSGI server run Flask-SocketIO well?

  • It can't hold many long-lived connections efficiently
  • It rejects JSON
  • It blocks emit()
  • It has no routing

Answer: It can't hold many long-lived connections efficiently. Flask-SocketIO needs an async server (eventlet, gevent, or socketio.run) to keep many connections open.

Continue this course

Frequently asked questions

How is a WebSocket different from a normal HTTP request?

HTTP is request-response: the client asks, the server answers, and the connection closes. A WebSocket opens a single long-lived, two-way connection so the server can push data to the client at any time without the client asking again. That makes it ideal for chat, live dashboards, notifications, and multiplayer features.

What is Flask-SocketIO?

Flask-SocketIO is an extension that adds Socket.IO (a real-time event protocol built on WebSockets, with fallbacks) to a Flask app. You register event handlers with @socketio.on('event') and push data to clients with emit(). It also manages connections, rooms, and broadcasting.

What are rooms in Socket.IO?

A room is a named group of connected clients. A client joins a room with join_room('name'), and the server can emit an event to just that room with emit(..., room='name'). Rooms let you target a subset of users — for example, everyone watching one game or chatting in one channel — instead of broadcasting to all.

What does emit() do, and what is broadcast?

emit('event', data) sends an event to a client. By default in a handler it goes back to the sender; pass broadcast=True to send it to every connected client, or room='name' to target a room. The client listens with socket.on('event', handler) to receive it.

Do I need a special server to run WebSockets?

Yes — a normal WSGI server handles one request at a time and can't hold many open connections efficiently. Flask-SocketIO runs on an async server (eventlet, gevent, or its own threading mode in development) so it can keep many long-lived connections open at once.