FastAPI's WebSocket State Machine
FastAPI manages WebSockets with a state machine, tracking client and application states separately. You must explicitly `accept()` a connection before communicating. This is key for chat or notification features.
WHY IT EXISTS WebSockets are stateful, unlike stateless HTTP requests. A server needs a robust way to manage the lifecycle of each connection: from the initial handshake to message exchange and eventual disconnection. The Starlette framework, which underpins FastAPI, provides this explicit state management to prevent race conditions and invalid operations on a connection that isn't ready.
THE MENTAL MODEL A WebSocket connection in FastAPI is not a simple open pipe; it's a state machine you must navigate correctly. The framework maintains two states: client_state (what the client is doing) and application_state (what your code has done). Both start in a CONNECTING state. Your primary job is to transition the application_state from CONNECTING to CONNECTED by calling websocket.accept(). Only then can you use send or receive methods.
HOW IT WORKS When a client initiates a connection, your endpoint receives a WebSocket object. Internally, both client_state and application_state are set to WebSocketState.CONNECTING. Your code must first call await websocket.accept(). This sends an acceptance message to the client and updates the server's application_state to WebSocketState.CONNECTED. After accepting, you can enter a loop to await websocket.receive_text() or other receive_* methods to listen for messages. If the client disconnects, a WebSocketDisconnect exception is raised, which you should catch in a try...except block to perform cleanup. To close the connection from the server side, you call await websocket.close().
WHEN TO USE IT This explicit state management flow is not optional; it is the required pattern for implementing any WebSocket endpoint in FastAPI or Starlette. It's essential for any real-time, bidirectional communication, such as in chat applications, live monitoring dashboards, or collaborative editing tools. You must always accept() before you receive() or send().
WHEN NOT TO USE IT This model is specific to WebSocket connections. For standard, stateless request-response cycles, you use regular FastAPI path operations like @app.get or @app.post. If your only need is to push data from the server to the client without receiving much back, Server-Sent Events (SSE) might be a simpler alternative that doesn't require this two-way state management.
ONE CANONICAL EXAMPLE A robust pattern is a try/finally block. The try block calls await websocket.accept(), then enters a while True loop to receive and send data, handling WebSocketDisconnect exceptions. The finally block ensures that cleanup code, like removing the user from a list of active connections, runs when the connection is closed for any reason. This structure guarantees that resources are released correctly, even on unexpected client disconnections.
Read the original → github.com
Get five bites like this every day.
Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.