FastAPI WebSocket Example
Introduction
WebSockets provide a persistent connection between a client and server, allowing for real-time, bidirectional communication. Unlike traditional HTTP requests where the client must initiate all interactions, WebSockets enable both the client and server to send messages at any time once a connection is established.
In this tutorial, we'll build a real-time chat application using FastAPI's WebSocket support. This example will demonstrate:
- Setting up WebSocket endpoints in FastAPI
- Managing WebSocket connections
- Broadcasting messages to connected clients
- Creating a simple HTML/JavaScript frontend to interact with our WebSocket server
By the end of this tutorial, you'll have a functional real-time chat application that you can adapt for your own projects.
Prerequisites
Before starting, ensure you have:
- Python 3.7+ installed
- Basic understanding of FastAPI
- Basic knowledge of HTML and JavaScript
Let's begin by installing the required packages:
pip install fastapi uvicorn
Building a Simple Chat Application
Step 1: Create the FastAPI Application
First, let's create a basic FastAPI application with WebSocket support. Create a file named app.py
:
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
from fastapi.responses import HTMLResponse
from typing import List
app = FastAPI()
# HTML content for our chat interface
html = """
<!DOCTYPE html>
<html>
<head>
<title>FastAPI Chat</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 1em;
}
#messages {
border: 1px solid #ddd;
height: 400px;
overflow-y: auto;
padding: 10px;
margin-bottom: 10px;
border-radius: 4px;
}
#messageForm {
display: flex;
}
#messageInput {
flex: 1;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
}
button {
background-color: #4CAF50;
color: white;
border: none;
padding: 9px 15px;
margin-left: 10px;
border-radius: 4px;
cursor: pointer;
}
.message {
margin-bottom: 5px;
padding: 5px;
}
.user-message {
background-color: #e9f5ff;
border-radius: 4px;
}
.system-message {
color: #666;
font-style: italic;
}
</style>
</head>
<body>
<h1>FastAPI Chat Example</h1>
<div id="messages"></div>
<form id="messageForm">
<input type="text" id="messageInput" placeholder="Type your message here" autocomplete="off"/>
<input type="text" id="usernameInput" placeholder="Your username" autocomplete="off"/>
<button type="submit">Send</button>
</form>
<script>
const messagesDiv = document.getElementById('messages');
const messageForm = document.getElementById('messageForm');
const messageInput = document.getElementById('messageInput');
const usernameInput = document.getElementById('usernameInput');
// Create WebSocket connection
const ws = new WebSocket(`ws://${window.location.host}/ws`);
ws.onmessage = function(event) {
const message = event.data;
const messageElement = document.createElement('div');
if (message.startsWith('User')) {
messageElement.className = 'message user-message';
} else {
messageElement.className = 'message system-message';
}
messageElement.textContent = message;
messagesDiv.appendChild(messageElement);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
};
messageForm.addEventListener('submit', function(event) {
event.preventDefault();
const message = messageInput.value;
const username = usernameInput.value || 'Anonymous';
if (message) {
ws.send(JSON.stringify({
username: username,
message: message
}));
messageInput.value = '';
}
});
</script>
</body>
</html>
"""
# Class to manage WebSocket connections
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 send_personal_message(self, message: str, websocket: WebSocket):
await websocket.send_text(message)
async def broadcast(self, message: str):
for connection in self.active_connections:
await connection.send_text(message)
manager = ConnectionManager()
@app.get("/")
async def get():
return HTMLResponse(html)
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await manager.connect(websocket)
try:
# Send a welcome message to the new client
await manager.send_personal_message("Connected to the chat server!", websocket)
# Broadcast that a new user has joined
await manager.broadcast(f"User #{len(manager.active_connections)} has joined the chat")
while True:
data = await websocket.receive_json()
username = data.get("username", "Anonymous")
message = data.get("message", "")
# Broadcast the message to all connected clients
await manager.broadcast(f"User {username}: {message}")
except WebSocketDisconnect:
manager.disconnect(websocket)
await manager.broadcast(f"User #{len(manager.active_connections) + 1} has left the chat")
Step 2: Run the Application
Now, let's run our FastAPI application:
uvicorn app:app --reload
Visit http://localhost:8000
in your web browser to see the chat interface. Open multiple browser windows to simulate different users chatting with each other.
Understanding the Code
Let's break down what's happening in our chat application:
The HTML Interface
The HTML content includes:
- A styled chat interface with a message display area
- A form for sending messages
- Input fields for the message and username
- JavaScript code to:
- Establish the WebSocket connection
- Handle incoming messages
- Send messages when the form is submitted
The ConnectionManager Class
This class is crucial for managing WebSocket connections:
active_connections
stores all connected clientsconnect()
accepts a new WebSocket connection and adds it to the listdisconnect()
removes a connection from the listsend_personal_message()
sends a message to a specific clientbroadcast()
sends a message to all connected clients
The WebSocket Endpoint
The /ws
endpoint handles WebSocket connections:
-
When a new client connects:
- The connection is accepted via
manager.connect()
- A welcome message is sent to the client
- A message is broadcast to all clients about the new user
- The connection is accepted via
-
While the connection is active:
- The endpoint receives JSON data containing a username and message
- The message is broadcast to all connected clients
-
When a client disconnects:
- The connection is removed via
manager.disconnect()
- A message is broadcast about the user leaving
- The connection is removed via
Enhancing the Example
Let's extend our example by adding a few more features:
Step 3: Add User Typing Indicators
Update app.py
to add typing indicators:
# Add this to the existing WebSocket endpoint
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await manager.connect(websocket)
try:
await manager.send_personal_message("Connected to the chat server!", websocket)
await manager.broadcast(f"User #{len(manager.active_connections)} has joined the chat")
while True:
data = await websocket.receive_json()
username = data.get("username", "Anonymous")
message = data.get("message", "")
typing = data.get("typing", False)
if typing:
# Only send typing indicators to other users
for connection in manager.active_connections:
if connection != websocket:
await connection.send_text(f"{username} is typing...")
elif message:
# Broadcast the message to all connected clients
await manager.broadcast(f"User {username}: {message}")
except WebSocketDisconnect:
manager.disconnect(websocket)
await manager.broadcast(f"User #{len(manager.active_connections) + 1} has left the chat")
And update the JavaScript in the HTML content:
// Add this after the existing JavaScript
let typingTimer;
messageInput.addEventListener('input', function() {
clearTimeout(typingTimer);
// Send typing indicator
ws.send(JSON.stringify({
username: usernameInput.value || 'Anonymous',
typing: true
}));
// Stop sending typing indicator after 1 second of inactivity
typingTimer = setTimeout(() => {
ws.send(JSON.stringify({
username: usernameInput.value || 'Anonymous',
typing: false
}));
}, 1000);
});
Real-World Applications
WebSockets in FastAPI can be used for various real-time applications:
-
Collaborative Editing Tools: Multiple users can edit documents simultaneously with changes reflected in real time.
-
Live Dashboards: Update metrics and charts without requiring page refreshes.
-
Multiplayer Games: Synchronize game states across multiple players.
-
Notification Systems: Push notifications to users instantly when events occur.
-
IoT Applications: Monitor and control IoT devices with minimal latency.
Performance Considerations
When working with WebSockets in production, keep the following in mind:
-
Connection Limits: Each WebSocket connection consumes server resources, so implement a strategy for limiting connections if needed.
-
Authentication: For secure applications, authenticate users before allowing WebSocket connections.
-
Heartbeats: Implement periodic heartbeat messages to detect and clean up stale connections.
-
Rate Limiting: Prevent abuse by implementing rate limits for message sending.
-
Scaling: Consider using Redis or other message brokers to broadcast messages across multiple server instances.
Summary
In this tutorial, we've built a real-time chat application using FastAPI's WebSocket support. We've learned how to:
- Create WebSocket endpoints in FastAPI
- Manage multiple WebSocket connections
- Send and broadcast messages
- Create a simple frontend to interact with our WebSocket server
- Add enhanced features like typing indicators
WebSockets provide a powerful way to implement real-time features in your web applications. FastAPI makes it straightforward to add WebSocket functionality with its simple and intuitive API.
Additional Exercises
- Add message persistence using a database (SQLite, PostgreSQL, etc.)
- Implement private messaging between specific users
- Add user authentication before allowing WebSocket connections
- Create separate chat rooms or channels
- Add file sharing capabilities
- Implement message read receipts
Additional Resources
- FastAPI WebSockets Documentation
- MDN WebSockets API
- WebSockets Protocol RFC6455
- Socket.IO - A popular alternative for real-time web applications
With these concepts and examples, you should now be well-equipped to implement WebSockets in your own FastAPI applications!
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)