Python to FastAPI WebSockets Guide
WebSockets enable real-time, bidirectional communication between a client and a server. Unlike traditional HTTP requests, which are request-response based, WebSockets maintain a persistent connection allowing data to flow continuously in both directions. Let us delve into understanding how to use WebSockets from Python to FastAPI.
1. Understanding WebSocket
A WebSocket is a communication protocol that enables full-duplex (two-way) communication over a single, long-lived TCP connection. Unlike traditional HTTP, which follows a request-response model, WebSockets allow both the client and server to send data at any time without repeatedly opening new connections. This makes them ideal for real-time applications such as chat apps, live notifications, multiplayer games, and streaming dashboards.
1.1 Key Features
- Persistent connection: Once established, the connection remains open, eliminating the overhead of repeated handshakes.
- Low latency communication: Data is transmitted instantly without HTTP request/response delays.
- Bi-directional data flow: Both client and server can independently send messages.
- Efficient resource usage: Reduces bandwidth and CPU usage compared to polling or long polling.
- Real-time capability: Ideal for applications requiring immediate updates.
1.2 WebSocket Connection Lifecycle
- Handshake: The connection starts as an HTTP request with an
Upgradeheader. If the server supports WebSockets, it upgrades the connection to a WebSocket protocol. - Open: Once upgraded, a persistent connection is established between client and server.
- Message Exchange: Data is exchanged in frames. Messages can be text or binary and flow in both directions.
- Ping/Pong (Keep Alive): Optional heartbeat mechanism to ensure the connection is still alive and detect stale connections.
- Close: Either the client or server can close the connection gracefully using a close frame.
1.3 Common Methods
connect()– Establishes the WebSocket connection.accept()– (FastAPI-specific) Accepts an incoming WebSocket request.send_text()/send_bytes()– Sends data to the client.receive_text()/receive_bytes()– Receives data from the client.close()– Closes the connection gracefully.
1.4 Handling Disconnections
WebSocket disconnections can occur due to several reasons:
- Client intentionally closing the connection (e.g., browser tab closed)
- Network interruptions or unstable connectivity
- Server-side timeout or restart
- Protocol errors or invalid messages
In production systems, it is important to handle disconnections gracefully to avoid memory leaks, dangling connections, or inconsistent application state. You may also implement reconnection logic on the client side.
from fastapi import WebSocket, WebSocketDisconnect
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
try:
while True:
data = await websocket.receive_text()
print(f"Received: {data}")
await websocket.send_text(f"Echo: {data}")
except WebSocketDisconnect:
print("Client disconnected")
You can also perform cleanup tasks inside the exception block, such as removing the client from an active connections list.
2. Code Example
This example brings everything together into one cohesive setup, including a FastAPI WebSocket server, a Python client, seamless file transfer over WebSockets, and proxying communication to an external WebSocket service. Before running the example, ensure you have the required dependencies installed. This setup uses FastAPI for building the WebSocket server, Uvicorn as the ASGI server, and the websockets library for both client communication and external WebSocket proxying.
pip install fastapi uvicorn websockets
Make sure you are using Python 3.8 or above for proper async support. Also, ensure that the required ports (default: 8000) are available and not blocked by firewall rules.
2.1 FastAPI Server (main.py)
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
import websockets
app = FastAPI()
@app.websocket("/ws")
async def websocket_handler(websocket: WebSocket):
await websocket.accept()
print("Client connected")
try:
while True:
message_type = await websocket.receive_text()
# TEXT MESSAGE FLOW
if message_type == "text":
data = await websocket.receive_text()
print(f"Received text: {data}")
await websocket.send_text(f"Echo: {data}")
# FILE TRANSFER FLOW
elif message_type == "file":
print("Receiving file...")
with open("received_file.txt", "wb") as f:
while True:
chunk = await websocket.receive_bytes()
if chunk == b"EOF":
break
f.write(chunk)
await websocket.send_text("File received successfully")
# PROXY FLOW (external websocket)
elif message_type == "proxy":
external_uri = "wss://echo.websocket.events"
async with websockets.connect(external_uri) as external_ws:
client_data = await websocket.receive_text()
await external_ws.send(client_data)
response = await external_ws.recv()
await websocket.send_text(f"External response: {response}")
except WebSocketDisconnect:
print("Client disconnected")
This code defines a WebSocket endpoint in FastAPI that handles multiple communication flows within a single persistent connection. The application initializes a FastAPI server and exposes a /ws WebSocket route, where incoming client connections are accepted using await websocket.accept(). Inside an infinite loop, the server first receives a message type indicator (such as “text”, “file”, or “proxy”) to determine how to process the subsequent data. For text messages, it receives a string using receive_text(), logs it, and sends back an echoed response. For file transfers, it switches to binary mode, continuously reading chunks using receive_bytes() until an EOF marker is received, writing the data to a local file. In the proxy flow, the server acts as an intermediary by establishing a connection to an external WebSocket server using the websockets library, forwarding client data, receiving a response from the external service, and relaying it back to the client. The entire interaction is wrapped in a try-except block to gracefully handle client disconnections using WebSocketDisconnect, ensuring stability and proper cleanup of the connection lifecycle.
2.1.1 Run Server
uvicorn main:app --reload
The above command starts the FastAPI application using Uvicorn, an ASGI server designed for asynchronous frameworks. The main:app refers to the app instance inside the main.py file. The --reload flag enables auto-reloading, which is useful during development as the server automatically restarts when code changes are detected. By default, the server runs on http://127.0.0.1:8000, and the WebSocket endpoint becomes available at ws://127.0.0.1:8000/ws. Once the server is running, it will listen for incoming WebSocket connections and log events such as client connections, messages received, file transfers, and disconnections in the console. Ensure the server is running before starting the client to establish a successful connection.
2.2 Python Client (client.py)
import asyncio
import websockets
async def send_text(websocket):
await websocket.send("text")
await websocket.send("Hello from client!")
response = await websocket.recv()
print("Text Response:", response)
async def send_file(websocket):
await websocket.send("file")
with open("sample.txt", "rb") as f:
while chunk := f.read(1024):
await websocket.send(chunk)
await websocket.send(b"EOF")
response = await websocket.recv()
print("File Response:", response)
async def proxy_message(websocket):
await websocket.send("proxy")
await websocket.send("Hello external socket!")
response = await websocket.recv()
print("Proxy Response:", response)
async def main():
uri = "ws://localhost:8000/ws"
async with websockets.connect(uri) as websocket:
await send_text(websocket)
await send_file(websocket)
await proxy_message(websocket)
asyncio.run(main())
This code implements an asynchronous Python WebSocket client using the asyncio and websockets libraries to interact with a FastAPI WebSocket server. It defines three separate async functions to handle different communication flows: send_text() sends a “text” identifier followed by a string message and waits for an echoed response from the server; send_file() initiates a file transfer by sending a “file” identifier, then reads a local file (sample.txt) in chunks of 1024 bytes and streams each chunk over the WebSocket, finally sending an EOF marker to signal completion and receiving a confirmation response; proxy_message() triggers the proxy flow by sending a “proxy” identifier along with a message that the server forwards to an external WebSocket and returns the response. The main() function establishes a connection to the WebSocket server at ws://localhost:8000/ws using an async context manager, ensuring proper connection handling, and sequentially executes all three flows over the same persistent connection. Finally, asyncio.run(main()) starts the event loop and runs the client, demonstrating how multiple interaction patterns can be efficiently handled over a single WebSocket session.
2.3 Code Run and Output
After running both the FastAPI WebSocket server and the Python client, the following outputs can be observed. These outputs demonstrate how different communication flows (text, file transfer, and proxying) are handled over a single persistent WebSocket connection.
2.3.1 Server Console
Client connected Received text: Hello from client! Receiving file... Client disconnected (after completion)
The server logs show the lifecycle of the connection. Initially, the client establishes a connection, followed by a text message being received and processed. The server then switches to file transfer mode, where it starts receiving binary chunks and writes them to a file. Once all operations are complete and the client closes the connection, the server detects the disconnection and logs it accordingly.
2.3.2 Client Console
Text Response: Echo: Hello from client! File Response: File received successfully Proxy Response: External response: Hello external socket!
The client output confirms successful round-trip communication for each flow. The text message is echoed back by the server, the file transfer is acknowledged after complete upload, and the proxy flow demonstrates that the server successfully forwarded the request to an external WebSocket service and returned its response.
2.3.3 File Output
received_file.txt → contains same content as sample.txt
The file output verifies that the binary data sent from the client was correctly reconstructed on the server side. The received_file.txt file should exactly match the original sample.txt, confirming that chunked file transfer over WebSockets works reliably without data loss.
3. Conclusion
WebSockets are essential for building real-time applications such as chat apps, live notifications, and streaming systems, as they enable continuous, low-latency communication between client and server. With FastAPI and Python, you can easily create scalable WebSocket servers, handle real-time bidirectional communication where both client and server can send and receive data independently, transfer files efficiently using binary streams, and integrate seamlessly with external WebSocket services for proxying or extending functionality. By mastering these patterns, developers can design highly responsive, efficient, and modern backend systems capable of supporting real-time user experiences at scale.



