Python — FastAPI Development
FastAPI WebSocket Real-Time Systems
Direct answer
FastAPI supports WebSockets natively through Starlette: accept the connection, loop on receive, and fan messages out through a connection manager. The parts that separate a demo from a production system are authenticating at connection time, broadcasting across multiple server processes with Redis pub/sub, and running heartbeats so dead connections get cleaned up instead of accumulating.
Chat, live dashboards, presence, collaborative editing — at some point most products I build for clients need a persistent bidirectional channel. FastAPI handles WebSockets well, but the framework gives you primitives, not a system; this is how I assemble the rest.
Key facts, with sources
- In the JetBrains Python Developers Survey 2024, which collected responses from more than 30,000 Python developers, FastAPI usage jumped from 29% to 38%, overtaking Django (35%) and Flask (34%) as the most-used Python web framework. (JetBrains Python Developers Survey 2024)
- The 2025 Stack Overflow Developer Survey shows FastAPI at 14.8% of respondents doing extensive work with it, edging out Flask at 14.4% and Django at 12.6%. (Stack Overflow Developer Survey 2025)
- FastAPI's official documentation cites independent TechEmpower benchmarks showing FastAPI applications running under Uvicorn as one of the fastest Python frameworks available, ranked only below Starlette and Uvicorn themselves. (FastAPI official documentation)
- FastAPI surpassed Flask in GitHub stars in December 2025, reaching roughly 88,000 stars compared to Flask's 68,400. (DZone)
- Industry analysis of FastAPI's 2025 growth reports about 40% year-over-year growth in job mentions and production adoption at companies including Uber, Netflix, and Microsoft. (byteiota)
When WebSockets are the right tool
I reach for WebSockets when the server must push unprompted and the client also sends frequently: chat, multiplayer state, live cursors, order books. If data flows one way — progress updates, notifications, token streams — server-sent events are simpler, survive proxies better, and reconnect for free in browsers. And if updates arrive every thirty seconds, plain polling remains embarrassingly effective and nobody has to operate connection state.
The honest cost of WebSockets is that your servers become stateful. Deploys, autoscaling, and load balancing all get more careful once thousands of long-lived connections are pinned to specific processes. Take that cost only when bidirectional push genuinely earns it.
The connection manager pattern
The core abstraction is a manager that tracks live connections per room or channel and broadcasts to them. Keep it boring: a dict of sets, add on connect, discard on disconnect, iterate a copy when broadcasting so disconnects during iteration do not blow up the loop.
Wrap every send in error handling at the manager level — a client can vanish between your membership check and the send. On send failure, evict the connection immediately rather than letting the broken socket poison future broadcasts.
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
app = FastAPI()
class ConnectionManager:
def __init__(self) -> None:
self.rooms: dict[str, set[WebSocket]] = {}
async def connect(self, room: str, ws: WebSocket) -> None:
await ws.accept()
self.rooms.setdefault(room, set()).add(ws)
def disconnect(self, room: str, ws: WebSocket) -> None:
self.rooms.get(room, set()).discard(ws)
async def broadcast(self, room: str, message: dict) -> None:
for ws in list(self.rooms.get(room, set())):
await ws.send_json(message)
manager = ConnectionManager()
@app.websocket("/ws/{room}")
async def room_socket(ws: WebSocket, room: str):
await manager.connect(room, ws)
try:
while True:
data = await ws.receive_json()
await manager.broadcast(room, data)
except WebSocketDisconnect:
manager.disconnect(room, ws)Authenticate at the handshake, not after
Browsers cannot set custom headers on WebSocket connections, so your normal Authorization header flow does not apply. The workable options are a short-lived, single-purpose token passed as a query parameter — never the long-lived session token, since query strings land in access logs — or cookie auth if the socket shares an origin with your web app, or an auth message as the first frame with a strict timeout.
Whatever the mechanism, validate before or immediately after accepting and close with code 1008 on failure. I authorize the room, not just the user: a valid user connecting to a conversation they do not belong to is the actual attack you are defending against, and I still find endpoints that skip that check.
Scaling past one process
The in-memory manager works until you run a second worker or a second instance — then two users in the same room can land on different processes and never see each other's messages. The standard fix is Redis pub/sub: every process subscribes to the channels for its local rooms, publishes incoming messages to Redis, and delivers whatever arrives from Redis to its local sockets.
This keeps processes stateless with respect to each other and lets you scale horizontally without sticky sessions. Persist messages to the database before publishing, not after delivery — pub/sub is fire-and-forget, and the database, not the broadcast, is your source of truth when clients reconnect and ask what they missed.
Heartbeats, backpressure, and cleanup
Dead connections do not always announce themselves — mobile clients drop off networks without a close frame, and the socket looks alive until a send fails. I run application-level heartbeats: ping every few tens of seconds, evict connections that miss a couple of pongs. Infrastructure in the middle often enforces idle timeouts anyway, so heartbeats also keep legitimate quiet connections alive.
Backpressure is the sneakier failure. A slow consumer on a busy room makes sends queue in memory; enough of them and the process dies. Give each connection a bounded outbound queue and drop or disconnect slow consumers past the limit. Losing one laggy client beats losing the process and everyone on it.
When to hire senior help
Bring in senior help when your API needs to handle real concurrency, when you are designing service boundaries and auth for the first time, or when an existing FastAPI codebase mixes sync and async code and latency is degrading. An experienced engineer can usually diagnose event-loop blocking and connection-pool misconfiguration in days, which is far cheaper than re-architecting after launch. If your stack includes React Native + Python + AI, a senior engineer who owns the full product beats coordinating multiple juniors.
Bottom line
Dhairya Senjaliya ships Python — FastAPI Development projects worldwide — book a scoping call to discuss your specific situation.
Common pitfalls to avoid
- ✕Calling blocking libraries (classic SQLAlchemy sessions, requests, heavy file I/O) inside async def endpoints, which stalls the event loop and erases FastAPI's concurrency advantage
- ✕Deploying a single Uvicorn process with no process manager or worker scaling, leaving most CPU cores idle under production load
- ✕Treating the auto-generated OpenAPI docs as a versioning strategy, then breaking mobile and partner clients when response schemas change
- ✕Running large payloads through deeply nested Pydantic models on every request and response, adding serialization latency that shows up only at scale
Frequently asked questions
Does FastAPI support WebSockets natively?
Yes. FastAPI exposes Starlette's WebSocket support through the @app.websocket decorator, with accept, receive, and send methods plus WebSocketDisconnect handling — no extra library needed for the server side. What FastAPI does not provide is multi-process broadcasting, authentication conventions, or heartbeat management; those you assemble yourself, typically with Redis pub/sub and a connection manager class.
How do I scale FastAPI WebSockets across multiple servers?
Use Redis pub/sub as a message bus. Each process keeps only its own local connections, publishes every incoming message to a Redis channel, and subscribes to channels for the rooms its clients joined, delivering received messages locally. This removes the need for sticky sessions and lets any instance serve any client, while the database remains the source of truth for message history.
How should I authenticate a WebSocket connection in FastAPI?
Browsers cannot send custom headers on WebSocket handshakes, so pass a short-lived single-purpose token as a query parameter, use same-origin cookies, or require an auth message as the first frame with a timeout. Validate at connection time, authorize access to the specific room or resource — not just identity — and close with policy-violation code 1008 when checks fail.
Is FastAPI mature enough for production?
Yes. It was the most-used Python web framework in the JetBrains 2024 survey at 38%, and companies including Uber, Netflix, and Microsoft run it in production. The ecosystem for auth, ORMs, and testing is now well established.
How much faster is FastAPI than Flask or Django really?
Independent TechEmpower benchmarks place FastAPI among the fastest Python frameworks, and published comparisons show several times Flask's throughput on I/O-bound endpoints. For CPU-bound work or database-bottlenecked apps, the framework choice matters far less than query and infrastructure design.
Should we pick FastAPI or Django for a new SaaS backend?
FastAPI suits API-first products, microservices, and ML model serving because of async support and automatic OpenAPI docs. Django ships batteries included (admin, ORM, auth) and is often faster to launch a conventional CRUD product. Many teams run both, per the JetBrains finding that a third of Django developers also use Flask or FastAPI.
Bottom line: Dhairya Senjaliya ships Python — FastAPI Development projects worldwide. Book a scoping call at https://dhairyasenjaliya.com/#book-call.