Fun with Learning Technology
LearnCoursesQuestionsTracksToolsNewsExplorePractice
Fun with Learning Technology

A new problem, explained clearly, every day.

Subscribe
Learn
  • Lessons
  • Topics
  • News
  • Tools
  • Courses
  • Career tracks
  • Everything
Site
  • About
  • Contact
  • Support
  • Privacy
  • Terms
Get the daily one

One email per new problem. No spam.

Request a tutorial

Requests shape what gets made next.

© 2026 Fun with Learning TechnologyRSS
Home›Courses›FastAPI›WebSockets in FastAPI

Advanced

WebSockets in FastAPI

WebSockets enable full-duplex, persistent communication channels between a client and server over a single TCP connection, moving beyond the request-response cycle. This technology is essential for building real-time applications where low latency and bidirectional data flow are critical requirements. You should reach for WebSockets when your application needs to push data to clients spontaneously, such as in live chat rooms, collaborative editing tools, or real-time notification systems.

Establishing a WebSocket Connection

To start a WebSocket connection, FastAPI uses the @app.websocket decorator. Unlike standard HTTP routes that handle a single request and return a response, a WebSocket endpoint represents a long-running lifecycle. The WebSocket class provided in the handler function acts as your interface to manage this connection. You must call `await websocket.accept()` to formalize the handshake; until this is called, the connection remains in a pending state. Once accepted, the connection stays open until either the client disconnects or the server terminates it. It is crucial to understand that the function body behaves like a persistent loop or a sequence of operations that maintain state for that specific connection. By invoking `accept()`, you allow the browser or client to start sending binary or text frames, which you then consume asynchronously using an awaitable receive method, ensuring the event loop is never blocked during long-lived sessions.

from fastapi import FastAPI, WebSocket

app = FastAPI()

@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    # Formalize the connection handshake
    await websocket.accept()
    while True:
        # Wait for data from the client
        data = await websocket.receive_text()
        # Echo the data back to the client
        await websocket.send_text(f"Message received: {data}")

Handling Data Streams and Concurrency

FastAPI leverages asynchronous programming to handle thousands of concurrent WebSocket connections efficiently. Because each connection is handled by an asynchronous coroutine, the server does not need to spawn a full system thread for every user, which would quickly exhaust system memory. When you use `await websocket.receive_text()`, the current task is suspended, yielding control back to the event loop so that other connections can be processed in the meantime. This non-blocking nature is the core reason FastAPI excels at real-time systems. As you design your data processing, keep in mind that receiving data is a blocking operation relative to your local coroutine, but non-blocking relative to the entire server process. You must wrap your logic in exception handling, particularly for `WebSocketDisconnect`, to clean up resources, close database sessions, or notify other participants when a specific user leaves the active communication channel prematurely.

from fastapi import FastAPI, WebSocket, WebSocketDisconnect

app = FastAPI()

@app.websocket("/stream")
async def stream_endpoint(websocket: WebSocket):
    await websocket.accept()
    try:
        while True:
            data = await websocket.receive_text()
            await websocket.send_text(f"Processed: {data}")
    except WebSocketDisconnect:
        # Clean up logic here when client closes
        print("Client disconnected successfully")

Broadcasting to Multiple Clients

Real-time systems often require broadcasting messages to multiple connected clients simultaneously. To achieve this, you need a way to track all active WebSocket connections in a shared state, such as a set or a list within a manager class. A ConnectionManager becomes the central authority that maintains the list of active websockets. When a message arrives from one client, the manager iterates through this list and sends the data to every registered connection. This pattern allows for the creation of rooms or channels where users can interact collectively. Because FastAPI is asynchronous, calling `await websocket.send_text()` in a loop is highly performant; the event loop manages the scheduling of these individual network write operations. You must ensure that your manager is thread-safe or, preferably, kept within the same asynchronous scope to prevent race conditions during the registration or removal of active connections from your internal registry.

class ConnectionManager:
    def __init__(self):
        self.active_connections: list[WebSocket] = []

    async def connect(self, websocket: WebSocket):
        await websocket.accept()
        self.active_connections.append(websocket)

    async def broadcast(self, message: str):
        for connection in self.active_connections:
            await connection.send_text(message)

manager = ConnectionManager()

Validating Input with WebSocket Middleware

Security remains a paramount concern when dealing with persistent connections. While standard HTTP middleware might authenticate a user, WebSockets are often established once and then left open for long periods, meaning you must perform initial validation during the handshake phase. You can check cookies, authorization headers, or query parameters inside the WebSocket endpoint before calling `await websocket.accept()`. If the validation fails, you can raise an exception or simply close the connection before it is fully established. This ensures that unauthorized users are denied access at the entry point of the connection lifecycle. Furthermore, you can apply custom validation logic to the data being sent over the socket by parsing incoming text frames into structured data models using type hints or validation libraries. This prevents malformed data from affecting the internal state of your server or causing unhandled exceptions within your message handling loops.

from fastapi import WebSocket, status

@app.websocket("/secure-ws")
async def secure_endpoint(websocket: WebSocket, token: str):
    # Validate the token before accepting
    if token != "secret-key":
        await websocket.close(code=status.WS_1008_POLICY_VIOLATION)
        return
    await websocket.accept()
    await websocket.send_text("Authentication successful")

Integrating with Asynchronous Background Tasks

Sometimes your WebSocket server needs to send updates to the client that were not triggered by a direct request from that specific user, such as an alert based on a database change or a timer. You can achieve this by running background tasks alongside your WebSocket handler. Using `asyncio.create_task`, you can spawn a separate coroutine that pushes data to the WebSocket while the main handler waits for user input. This pattern is essential for implementing server-initiated events. However, you must take care to manage the lifecycle of these background tasks; if the WebSocket connection closes, the associated background task might continue to run and attempt to write to a closed socket, resulting in an error. Always check the connection status or use structured task groups to ensure that background workers are cancelled immediately upon the termination of the primary WebSocket session, maintaining server stability and preventing memory leaks in your application.

import asyncio

async def send_periodic_updates(websocket: WebSocket):
    while True:
        await asyncio.sleep(5)
        await websocket.send_text("Background system pulse")

@app.websocket("/pulse")
async def pulse_endpoint(websocket: WebSocket):
    await websocket.accept()
    task = asyncio.create_task(send_periodic_updates(websocket))
    try:
        await websocket.receive_text()
    finally:
        task.cancel() # Ensure background task stops when connection ends

Key points

  • WebSockets provide a full-duplex communication channel over a single persistent TCP connection.
  • The WebSocket handshake must be explicitly accepted using the accept method before any data can be exchanged.
  • FastAPI utilizes asynchronous event loops to handle high volumes of concurrent WebSocket connections efficiently.
  • Connection management is necessary for tracking active users and broadcasting messages across multiple clients.
  • Input validation should occur during the handshake phase to ensure only authenticated users maintain a connection.
  • Exception handling for disconnection events is vital for cleaning up server resources and managing state.
  • Asynchronous background tasks allow the server to push updates to clients independently of user requests.
  • Proper lifecycle management of background tasks is required to avoid attempting writes to closed socket connections.

Common mistakes

  • Mistake: Attempting to use standard dependencies like Depends() inside the body of a WebSocket handler. Why it's wrong: WebSocket endpoints are handled differently than HTTP routes, and certain dependency injection features are not supported within the active communication loop. Fix: Handle dependencies during the initial handshake or manually within the scope of the connection.
  • Mistake: Forgetting to await the connection acceptance. Why it's wrong: FastAPI WebSockets remain in a pending state until you explicitly call websocket.accept(). Fix: Always call await websocket.accept() as the first step within the WebSocket endpoint.
  • Mistake: Blocking the event loop with synchronous, long-running CPU tasks inside the WebSocket receive loop. Why it's wrong: Because WebSockets are async, blocking the event loop stops the entire server from processing other incoming connections or messages. Fix: Use run_in_threadpool or offload heavy processing to a background task.
  • Mistake: Assuming that the client will always gracefully close the connection. Why it's wrong: Network issues or abrupt browser closures cause WebSocketDisconnect exceptions that are often ignored. Fix: Wrap the message loop in a try-except block catching WebSocketDisconnect to clean up resources properly.
  • Mistake: Managing state (like a list of active users) globally without thread safety in mind. Why it's wrong: Even though FastAPI is async, concurrency can lead to race conditions if multiple connections modify shared state simultaneously. Fix: Use appropriate async synchronization primitives or a dedicated manager class.

Interview questions

What is the basic purpose of WebSockets in a FastAPI application?

WebSockets in FastAPI are designed to provide a persistent, full-duplex communication channel between the client and the server over a single TCP connection. Unlike standard HTTP requests that follow a request-response cycle, WebSockets allow the server to push real-time updates to the client without the client needing to explicitly request data each time. This is essential for applications requiring low-latency bi-directional data flow, such as live chat platforms, real-time dashboards, or interactive collaborative tools, effectively reducing the overhead of repeated headers and handshakes.

How do you define a WebSocket endpoint in FastAPI using the @app.websocket decorator?

To define a WebSocket endpoint, you use the @app.websocket('/path') decorator on an asynchronous function. Inside the function, you must accept the connection using 'await websocket.accept()'. Once the connection is established, you typically use an infinite loop to receive messages using 'data = await websocket.receive_text()' and send responses back using 'await websocket.send_text(f'Message text is: {data}')'. The decorator ensures that FastAPI handles the complex WebSocket handshake process automatically, allowing you to focus on the business logic of processing incoming data and managing the lifecycle of the connection.

How do you handle multiple connected clients in FastAPI when broadcasting data?

Handling multiple clients requires managing a collection of active WebSocket connections, usually stored in a list or a set within a connection manager class. When a new client connects, you append the websocket object to this list. When you need to broadcast a message, you iterate over the stored connections and call 'await connection.send_text(message)' for each one. It is critical to wrap these operations in try-except blocks because if a client disconnects unexpectedly, the send operation will raise a WebSocketDisconnect exception, which must be handled to remove the dead connection from your tracking list.

Compare the use of 'Depends' with WebSockets versus standard FastAPI HTTP routes.

While both HTTP routes and WebSocket endpoints support FastAPI's dependency injection system, they behave differently during execution. In an HTTP route, dependencies are resolved once for that specific request. In a WebSocket, you can use 'Depends' during the initial handshake, but the dependency is typically resolved at the start of the connection. If your dependency relies on request state that changes frequently, you must manually manage that within the WebSocket loop, whereas, in HTTP routes, the stateless nature ensures dependencies are fresh every time. Use dependencies in WebSockets primarily for authentication or shared database sessions that persist for the duration of the long-running socket connection.

What is the significance of the WebSocketDisconnect exception in FastAPI?

The WebSocketDisconnect exception is a vital tool for gracefully managing the lifecycle of a client connection. When a client closes their browser tab or loses network connectivity, FastAPI raises this exception inside your websocket endpoint loop. Without catching this specific exception, your server code might crash or enter an infinite loop trying to write to a closed socket. By catching 'WebSocketDisconnect', you can perform essential cleanup tasks, such as removing the user from an active channel, logging the departure, or updating the database to show the user as offline, ensuring that your application state remains consistent and memory leaks are prevented.

Explain how to handle background tasks or external events while keeping a WebSocket connection open in FastAPI.

To handle external events while maintaining an open WebSocket, you should avoid blocking the main event loop. Using 'asyncio.create_task' allows you to run background processes that listen for events, such as messages from a Redis queue, while the main loop continues to await 'websocket.receive_text()'. You should communicate between the background task and the WebSocket loop using 'asyncio.Queue'. The background task puts data into the queue, and the WebSocket function checks that queue, allowing the server to push asynchronous updates to the client as soon as data arrives, without hindering the user's ability to send their own messages through the same connection.

All FastAPI interview questions →

Check yourself

1. What happens if you fail to call 'await websocket.accept()' inside a WebSocket endpoint in FastAPI?

  • A.The server automatically accepts the connection.
  • B.The server throws an error when trying to receive or send messages.
  • C.The connection is closed immediately by the client.
  • D.The server hangs indefinitely without responding to the client.
Show answer

B. The server throws an error when trying to receive or send messages.
Calling accept() is required to complete the WebSocket handshake. If omitted, subsequent operations like receive_text() will fail because the underlying protocol upgrade hasn't occurred. The other options imply incorrect behaviors about automatic handshakes or hang states that do not match the FastAPI execution model.

2. Why should you wrap your WebSocket receive loop in a 'try-except WebSocketDisconnect' block?

  • A.To prevent the server from crashing when the client disconnects abruptly.
  • B.To allow the server to reconnect to the client automatically.
  • C.To bypass the need to define an explicit disconnect logic.
  • D.To speed up the message processing rate.
Show answer

A. To prevent the server from crashing when the client disconnects abruptly.
When a client leaves, FastAPI raises a WebSocketDisconnect exception. If not caught, this bubbles up and terminates the route function unexpectedly. Catching it allows you to clean up state or log the closure gracefully. Other options are incorrect as they misidentify the purpose of error handling as performance or automation tools.

3. Which of the following is the most appropriate way to handle a long-running CPU task triggered by a WebSocket message?

  • A.Run the task directly in the synchronous function.
  • B.Use a loop to keep the connection alive while processing.
  • C.Use 'starlette.concurrency.run_in_threadpool' or a background task.
  • D.Use 'asyncio.sleep()' to pause the connection until the task finishes.
Show answer

C. Use 'starlette.concurrency.run_in_threadpool' or a background task.
Offloading to a thread pool or background task prevents blocking the main event loop, which is critical for maintaining responsive WebSocket communication. Direct execution blocks the loop, and sleep/loop approaches are inefficient or incorrect patterns for concurrency.

4. How does FastAPI handle multiple concurrent WebSocket connections?

  • A.By spawning a new OS process for every connection.
  • B.By using an asynchronous event loop to handle connections concurrently.
  • C.By serializing connections and processing one at a time.
  • D.By limiting total connections to a single main thread.
Show answer

B. By using an asynchronous event loop to handle connections concurrently.
FastAPI leverages the power of async/await, allowing the event loop to switch between connections as they become ready for I/O, enabling thousands of concurrent connections. Processes are not spawned per connection, and serialization would defeat the purpose of asynchronous programming.

5. If you need to broadcast a message to all connected clients, why is a simple global list of connections often insufficient?

  • A.Global lists are not accessible inside WebSocket functions.
  • B.FastAPI prohibits global variable usage.
  • C.Managing the list requires thread-safe or async-safe operations to prevent race conditions during add/remove actions.
  • D.Broadcasting is only supported via external message brokers like Redis.
Show answer

C. Managing the list requires thread-safe or async-safe operations to prevent race conditions during add/remove actions.
While global lists are accessible, they are not atomic. Concurrent additions or removals while iterating over the list can cause errors. A connection manager class with async locks or safe collection handling is required. The other options suggest that such patterns are disallowed, which is false; they are simply prone to concurrency bugs.

Take the full FastAPI quiz →

← PreviousAlembic MigrationsNext →Server-Sent Events

FastAPI

24 lessons, free to read.

All lessons →

Track your progress

Sign in to mark lessons done, score quizzes and keep notes.

Open in the app