FastAPI WebSocket Routes
In FastAPI, WebSocket routes allow you to establish persistent, bidirectional communication channels between clients and your server. Unlike traditional HTTP endpoints that follow a request-response pattern, WebSockets enable continuous data exchange, making them perfect for applications requiring real-time updates like chat applications, live dashboards, or online games.
Understanding WebSocket Routing
WebSocket routes in FastAPI are defined similarly to regular HTTP endpoints but use a different decorator and handling mechanism to maintain the long-lived connection.
Basic WebSocket Route
Let's start with a simple WebSocket route:
from fastapi import FastAPI, WebSocket
app = FastAPI()
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
while True:
data = await websocket.receive_text()
await websocket.send_text(f"Message received: {data}")
In this basic example:
- We use the
@app.websocket
decorator to define a WebSocket endpoint at the "/ws" path - The function receives a
WebSocket
instance as a parameter - We accept the connection with
await websocket.accept()
- We enter a loop that continuously receives and sends messages
WebSocket Route Paths
Like REST endpoints, WebSocket routes can have:
Fixed Paths
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
# Handle connection
Path Parameters
Path parameters allow dynamic values in your WebSocket routes:
@app.websocket("/ws/{client_id}")
async def websocket_endpoint(websocket: WebSocket, client_id: str):
await websocket.accept()
await websocket.send_text(f"Connected to client: {client_id}")
while True:
data = await websocket.receive_text()
await websocket.send_text(f"Client {client_id} sent: {data}")
This is particularly useful for identifying different clients or channels in applications like chat rooms or user-specific dashboards.
WebSocket Connection Managers
For more complex applications, especially those with multiple clients connecting simultaneously, it's helpful to create a connection manager class:
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from typing import List
app = FastAPI()
class ConnectionManager:
def __init__(self):
self.active_connections: List[WebSocket] = []
async def connect(self, websocket: WebSocket):
await websocket.accept()
self.active_connections.append(websocket)
def disconnect(self, websocket: WebSocket):
self.active_connections.remove(websocket)
async def broadcast(self, message: str):
for connection in self.active_connections:
await connection.send_text(message)
manager = ConnectionManager()
@app.websocket("/ws/{client_id}")
async def websocket_endpoint(websocket: WebSocket, client_id: str):
await manager.connect(websocket)
try:
while True:
data = await websocket.receive_text()
# You can process the data here
await manager.broadcast(f"Client #{client_id} says: {data}")
except WebSocketDisconnect:
manager.disconnect(websocket)
await manager.broadcast(f"Client #{client_id} left the chat")
This connection manager pattern allows you to:
- Track all active connections
- Send messages to all connected clients (broadcasting)
- Handle client disconnections gracefully
Organizing WebSocket Routes with APIRouter
For larger applications, you can organize WebSocket routes using FastAPI's APIRouter
, just like with regular HTTP routes:
from fastapi import APIRouter, WebSocket
router = APIRouter()
@router.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
while True:
data = await websocket.receive_text()
await websocket.send_text(f"You sent: {data}")
# In your main app file
from fastapi import FastAPI
from .routes import router
app = FastAPI()
app.include_router(router, prefix="/chat")
Now your WebSocket endpoint will be available at "/chat/ws".
Real-World Example: Chat Application
Let's build a simple chat application to demonstrate WebSocket routes in action:
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.responses import HTMLResponse
from typing import List, Dict
app = FastAPI()
# HTML for a basic chat client
html = """
<!DOCTYPE html>
<html>
<head>
<title>FastAPI Chat</title>
</head>
<body>
<h1>FastAPI Chat</h1>
<h2>Your ID: <span id="ws-id"></span></h2>
<input type="text" id="messageText" placeholder="Type a message"/>
<button onclick="sendMessage()">Send</button>
<ul id="messages">
</ul>
<script>
var client_id = Date.now()
document.querySelector("#ws-id").textContent = client_id;
var ws = new WebSocket(`ws://localhost:8000/ws/${client_id}`);
ws.onmessage = function(event) {
var messages = document.getElementById('messages')
var message = document.createElement('li')
var content = document.createTextNode(event.data)
message.appendChild(content)
messages.appendChild(message)
};
function sendMessage() {
var messageInput = document.getElementById('messageText')
ws.send(messageInput.value)
messageInput.value = ''
}
</script>
</body>
</html>
"""
class ChatConnectionManager:
def __init__(self):
self.active_connections: Dict[str, WebSocket] = {}
async def connect(self, client_id: str, websocket: WebSocket):
await websocket.accept()
self.active_connections[client_id] = websocket
def disconnect(self, client_id: str):
if client_id in self.active_connections:
del self.active_connections[client_id]
async def send_personal_message(self, message: str, client_id: str):
if client_id in self.active_connections:
await self.active_connections[client_id].send_text(message)
async def broadcast(self, message: str, exclude_client: str = None):
for client_id, connection in self.active_connections.items():
if client_id != exclude_client:
await connection.send_text(message)
manager = ChatConnectionManager()
@app.get("/")
async def get():
return HTMLResponse(html)
@app.websocket("/ws/{client_id}")
async def websocket_endpoint(websocket: WebSocket, client_id: str):
await manager.connect(client_id, websocket)
try:
await manager.broadcast(f"Client #{client_id} joined the chat")
while True:
data = await websocket.receive_text()
await manager.broadcast(f"Client #{client_id}: {data}", exclude_client=client_id)
await manager.send_personal_message(f"You: {data}", client_id)
except WebSocketDisconnect:
manager.disconnect(client_id)
await manager.broadcast(f"Client #{client_id} left the chat")
This example demonstrates:
- A more advanced connection manager that maps client IDs to their WebSocket connections
- Broadcasting messages to all clients except the sender
- Sending personal messages to specific clients
- Providing a simple HTML interface that connects to our WebSocket endpoint
Best Practices for WebSocket Routes
- Error Handling: Always wrap your WebSocket handling code in try/except blocks to gracefully handle disconnections.
try:
while True:
data = await websocket.receive_text()
# Process data
except WebSocketDisconnect:
# Clean up resources
- Connection Timeouts: Implement timeout mechanisms for inactive connections:
import asyncio
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
try:
while True:
# Set a timeout of 60 seconds
data = await asyncio.wait_for(
websocket.receive_text(),
timeout=60.0
)
await websocket.send_text(f"You sent: {data}")
except asyncio.TimeoutError:
await websocket.close(code=1000, reason="Session timeout")
except WebSocketDisconnect:
# Handle disconnect
- Authentication: Protect your WebSocket routes with authentication:
from fastapi import Depends, Cookie, Query
async def get_user(
token: str = Query(None),
session: str = Cookie(None)
):
if token is None and session is None:
return None
# Validate token or session
# Return user or None
@app.websocket("/ws")
async def websocket_endpoint(
websocket: WebSocket,
user: dict = Depends(get_user)
):
if user is None:
await websocket.close(code=1008, reason="Not authenticated")
return
await websocket.accept()
# Proceed with authenticated user
Summary
WebSocket routes in FastAPI provide a powerful way to implement real-time communication features in your applications. By understanding how to:
- Create basic WebSocket endpoints
- Use path parameters for dynamic routing
- Implement connection managers for handling multiple clients
- Organize routes with
APIRouter
- Apply authentication and error handling
You can build robust real-time applications like chat systems, live dashboards, collaborative tools, and more.
Exercises
- Build a simple echo server that returns whatever message the client sends
- Create a WebSocket-based notification system that sends periodic updates to all connected clients
- Implement a chat room system where users can join different rooms based on room IDs
- Add authentication to your WebSocket routes using JWT tokens
- Create a live collaborative drawing application where multiple users can draw on the same canvas
Additional Resources
- FastAPI WebSockets Documentation
- MDN WebSocket API Documentation
- WebSockets Protocol RFC6455
- Socket.IO - Another popular library for real-time web applications
Happy coding with FastAPI WebSockets!
If you spot any mistakes on this website, please let me know at feedback@compilenrun.com. I’d greatly appreciate your feedback! :)