Python Server-Sent Events Patterns — Core Concepts
How SSE Works
Server-Sent Events use a persistent HTTP connection where the server writes text events in a specific format. The browser’s native EventSource API consumes the stream, parses events, and delivers them to JavaScript callbacks.
The protocol is simple. The server responds with Content-Type: text/event-stream and writes lines:
data: {"price": 142.50, "symbol": "AAPL"}
data: {"price": 143.10, "symbol": "AAPL"}
Each event ends with a blank line. The browser fires a message event for each block.
Named Events and IDs
Events can carry a type and an ID:
id: 1001
event: price-update
data: {"price": 142.50}
id: 1002
event: trade-alert
data: {"volume": 50000}
Named events let the client listen selectively. The id field enables reconnection recovery: when the connection drops, the browser sends a Last-Event-ID header with the last received ID. The server can use this to replay missed events.
Python Implementation Patterns
FastAPI / Starlette
FastAPI’s StreamingResponse makes SSE straightforward:
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import asyncio
app = FastAPI()
async def event_generator():
counter = 0
while True:
counter += 1
yield f"id: {counter}\ndata: {{\"count\": {counter}}}\n\n"
await asyncio.sleep(1)
@app.get("/events")
async def stream_events():
return StreamingResponse(
event_generator(),
media_type="text/event-stream",
headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
)
The X-Accel-Buffering: no header tells Nginx to disable response buffering, which otherwise delays SSE delivery.
Flask
Flask requires a generator with the WSGI streaming response:
from flask import Flask, Response
app = Flask(__name__)
def generate():
for i in range(100):
yield f"data: {{\"count\": {i}}}\n\n"
time.sleep(1)
@app.route("/events")
def stream():
return Response(generate(), content_type="text/event-stream")
Note that Flask’s synchronous nature limits concurrency. Each SSE connection ties up a worker thread. For high connection counts, use an async framework.
SSE vs WebSockets
| Aspect | SSE | WebSockets |
|---|---|---|
| Direction | Server to client only | Bidirectional |
| Protocol | HTTP | Upgraded TCP |
| Auto-reconnect | Built into EventSource | Manual implementation |
| Binary data | Not supported | Supported |
| Proxy compatibility | Excellent (plain HTTP) | Requires proxy configuration |
| Browser support | All modern browsers | All modern browsers |
Choose SSE when: the server pushes updates and the client only listens. Choose WebSockets when: the client also sends data frequently (chat, gaming, collaborative editing).
Reconnection Behavior
When an SSE connection drops, EventSource waits (default 3 seconds) and reconnects automatically. The server can control retry timing:
retry: 5000
data: connection established
The retry field sets the reconnection delay in milliseconds. Combined with event IDs, this creates a robust delivery mechanism — the client reconnects, tells the server its last ID, and receives missed events.
Common Misconception
Many developers assume SSE connections are expensive because they stay open. In practice, an idle SSE connection consumes very little — just a TCP socket and a small buffer. An async Python server can hold thousands of idle SSE connections with minimal resource usage. The cost grows only when the server actively pushes high-frequency events to many clients simultaneously.
The one thing to remember: SSE is the right tool when your Python server needs to push real-time updates to browsers — it is simpler than WebSockets, works through proxies, and includes automatic reconnection out of the box.
See Also
- Python Aiohttp Server Build a web server in Python that handles thousands of visitors without breaking a sweat.
- Python Websocket Scaling Why keeping thousands of live chat connections open in Python is like managing a phone switchboard that never hangs up.
- Ci Cd Why big apps can ship updates every day without turning your phone into a glitchy mess — CI/CD is the behind-the-scenes quality gate and delivery truck.
- Containerization Why does software that works on your computer break on everyone else's? Containers fix that — and they're why Netflix can deploy 100 updates a day without the site going down.
- Python 310 New Features Python 3.10 gave programmers a shape-sorting machine, friendlier error messages, and cleaner ways to say 'this or that' in type hints.