1.067 WebSocket Libraries#


Explainer

Domain Explainer: WebSocket Libraries#

What Is WebSocket?#

HTTP is a request/response protocol: the client asks, the server answers, and the connection is typically closed or kept alive but idle. If the server has new data, it can’t send it until the client asks again.

WebSocket solves this: it’s a persistent, full-duplex communication channel over a single TCP connection. Both the client and the server can send messages at any time, without the other side asking first.

The connection starts as a normal HTTP request — the client sends an Upgrade: websocket header — and the server responds with 101 Switching Protocols. After that, the TCP connection is repurposed: it no longer speaks HTTP, it speaks WebSocket’s own framing protocol.


When Do You Need WebSocket?#

The classic examples:

  • Chat applications: New messages must appear instantly without polling
  • Live dashboards: Stock prices, system metrics, sports scores updating in real time
  • Collaborative tools: Multiple users editing a document simultaneously
  • Multiplayer games: Player positions, game state needs low-latency bidirectional updates
  • IoT/device telemetry: Devices streaming sensor data, server sending commands

If you only need the server to push data (no client messages), consider Server-Sent Events (SSE) instead — simpler protocol, better proxy support, automatic reconnect. WebSocket is for when the client also needs to send.

If you’re doing normal request/response (REST API), you don’t need WebSocket at all.


How WebSocket Actually Works#

After the HTTP upgrade handshake, communication uses frames:

[FIN][opcode][MASK][payload length][optional extended length][optional mask][payload]

Two main frame types:

  • Text frames: payload must be valid UTF-8
  • Binary frames: payload is arbitrary bytes

This distinction matters: if you send json.dumps(data).encode() (Python bytes), you get a binary frame, even though it’s JSON-formatted. Send json.dumps(data) (a Python str) to get a text frame.

Built into the protocol: ping/pong frames for keep-alive. The server periodically sends a ping; the client must respond with pong. This prevents idle connections from being killed by intermediary proxies.


The Python Ecosystem#

websockets is the canonical Python library. Pure asyncio, actively maintained, clean API:

import websockets

async def handler(websocket):
    async for message in websocket:    # loop until connection closes
        await websocket.send(f"You said: {message}")

async with websockets.serve(handler, "localhost", 8765):
    await asyncio.get_event_loop().run_forever()

If you’re building a FastAPI application, use Starlette’s built-in WebSocket (it’s included with FastAPI — no extra library):

@app.websocket("/ws")
async def ws_endpoint(websocket: WebSocket):
    await websocket.accept()
    data = await websocket.receive_text()
    await websocket.send_text(f"Echo: {data}")

Socket.IO: WebSocket with Extras#

Socket.IO is a higher-level protocol that runs on top of WebSocket. It adds:

  • Named events: socket.emit('chat', data) vs plain ws.send(data)
  • Rooms: socket.join('room1') + io.to('room1').emit(...) — broadcast to groups
  • Automatic reconnect: The client automatically reconnects with backoff
  • Polling fallback: Works even if WebSocket is blocked by a corporate proxy

The catch: Socket.IO clients can only connect to Socket.IO servers, and vice versa. It’s not compatible with the raw WebSocket protocol. If your users might connect with the browser’s native new WebSocket(), don’t use Socket.IO.

python-socketio is the Python server implementation that speaks the Socket.IO protocol.


The Node.js Ecosystem#

ws is the standard Node.js WebSocket library: minimal, fast, 60 million downloads per week.

socket.io adds the Socket.IO protocol layer on top of ws.

uWebSockets.js is a C++ binding for extreme throughput — about 10x faster than ws, but with a complex API. Only needed above ~50K concurrent connections or when ws is a measured bottleneck.


Reverse Proxy Configuration#

In production, you don’t expose the WebSocket server directly. nginx or Caddy sits in front and handles TLS. Two things matter:

nginx needs special configuration:

location /ws {
    proxy_pass http://127.0.0.1:8765;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "Upgrade";
    proxy_read_timeout 3600s;  # don't kill idle connections
}

Caddy handles this automatically — no special configuration needed for WebSocket.

The proxy_read_timeout (nginx default: 60 seconds) kills idle WebSocket connections. Either increase it or configure ping/pong keep-alive on the server (websockets default: ping every 20 seconds — which keeps connections alive through nginx’s 60s timeout).


What This Domain Is NOT#

  • HTTP/2 server push: Server pushes HTTP responses; deprecated and removed from most browsers
  • Long-polling: Simulates bidirectional communication with repeated HTTP requests; much higher overhead
  • SSE (Server-Sent Events): Server-to-client only; simpler but unidirectional
  • WebRTC DataChannel: Peer-to-peer (browser-to-browser) data; different use case from server-client WebSocket
  • WebTransport: The emerging standard based on HTTP/3/QUIC; will eventually complement WebSocket but not ready for production in 2026
S1: Rapid Discovery

S1 Approach: Rapid Discovery#

Objective: Map the WebSocket library ecosystem for Python (server + client) and JavaScript/Node.js. Identify the primary candidates and initial verdicts.

Topics examined:

  1. Python async WebSocket servers: websockets, aiohttp WS, Starlette/FastAPI WS
  2. Python Socket.IO server (python-socketio)
  3. Node.js WebSocket servers: ws, uWebSockets.js
  4. Real-time framework layer: Socket.IO (server + client)
  5. Client-side: browser native WebSocket, reconnecting-websocket
  6. Key protocol facts: RFC 6455, HTTP Upgrade handshake, frame format

S1 Overview: WebSocket Libraries#

Protocol Foundation#

WebSocket (RFC 6455, 2011): Full-duplex communication over a single TCP connection. The connection starts as an HTTP/1.1 Upgrade request, then the protocol switches — the TCP socket is “hijacked” for bidirectional framing.

Key properties:

  • Persistent connection (unlike HTTP request/response)
  • Low overhead per message (2-10 byte header vs HTTP headers)
  • Text frames (UTF-8) and binary frames (arbitrary bytes)
  • Built-in ping/pong keep-alive at the protocol level
  • Same-origin policy applies in browsers (but servers can allow cross-origin)

HTTP Upgrade handshake:

Client → GET /ws HTTP/1.1
         Upgrade: websocket
         Connection: Upgrade
         Sec-WebSocket-Key: <base64 random>
         Sec-WebSocket-Version: 13

Server → HTTP/1.1 101 Switching Protocols
         Upgrade: websocket
         Connection: Upgrade
         Sec-WebSocket-Accept: <sha1 of key + magic>

After the 101 response, the TCP connection speaks WebSocket framing, not HTTP.


Python Ecosystem#

websockets#

PyPI: websockets | GitHub: python-websockets/websockets | ~180M downloads/month Current version: 16.0 (Jan 2026) | Language: Pure Python (asyncio)

The canonical Python WebSocket library. Used by Nym (websocket client), many Django/FastAPI chat examples.

Server pattern:

import asyncio
import websockets

async def handler(websocket):
    async for message in websocket:
        await websocket.send(f"Echo: {message}")

async def main():
    async with websockets.serve(handler, "localhost", 8765):
        await asyncio.get_event_loop().run_forever()

asyncio.run(main())

Client pattern:

async with websockets.connect("ws://localhost:8765") as ws:
    await ws.send("hello")
    reply = await ws.recv()

aiohttp WebSocket#

PyPI: aiohttp | Part of aiohttp server framework When to use: When you’re already using aiohttp for HTTP and want WebSocket in the same app.

from aiohttp import web

async def websocket_handler(request):
    ws = web.WebSocketResponse()
    await ws.prepare(request)
    async for msg in ws:
        if msg.type == web.WSMsgType.TEXT:
            await ws.send_str(f"Echo: {msg.data}")
    return ws

app = web.Application()
app.router.add_get('/ws', websocket_handler)

Starlette / FastAPI WebSocket#

PyPI: starlette, fastapi | ASGI-based WebSocket When to use: When building a FastAPI application that needs WebSocket endpoints alongside REST.

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"Echo: {data}")

Starlette’s WebSocket class wraps the ASGI scope/receive/send interface. Works with any ASGI server (uvicorn, hypercorn).

python-socketio#

PyPI: python-socketio | Socket.IO server for Python When to use: When you need Socket.IO protocol compatibility (rooms, namespaces, events, fallback).

Socket.IO is a higher-level protocol on top of WebSocket. It adds:

  • Named events (not just messages)
  • Rooms (broadcast to groups)
  • Namespaces (virtual channels)
  • Automatic reconnection (client handles)
  • Polling fallback (for environments blocking WebSocket)

JavaScript / Node.js Ecosystem#

ws#

npm: ws | ~147M weekly downloads | GitHub: websockets/ws Current version: 8.x | Most popular Node.js WebSocket library.

const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', function connection(ws) {
  ws.on('message', function message(data) {
    ws.send(`Echo: ${data}`);
  });
});

Minimal and fast. No protocol abstractions — raw WebSocket frames.

uWebSockets.js#

npm: uWebSockets.js | C++ binding via Node.js native addons When to use: Extreme throughput requirements (millions of messages/second).

Performance: ~10x throughput of ws at high connection counts, but much more complex API.

Socket.IO#

npm: socket.io (server) + socket.io-client | ~9.7M weekly downloads (v4.8.3, Dec 2025) Built on top of ws. Adds the Socket.IO protocol: named events, rooms, namespaces, reconnect.

const { Server } = require('socket.io');
const io = new Server(3000);

io.on('connection', (socket) => {
  socket.on('chat message', (msg) => {
    io.emit('chat message', msg);  // broadcast to all
  });
});

Quick Verdict Table#

LibraryLanguageUse CaseVerdict
websocketsPythonPure Python async WS server/client✅ Best path
aiohttp WSPythonWS in existing aiohttp app✅ Good when already using aiohttp
Starlette WSPythonWS in FastAPI app✅ Good, integrates with FastAPI DI
python-socketioPythonSocket.IO protocol server✅ If you need Socket.IO features
picowsPythonHigh-throughput Python WS (Cython)⚠️ When websockets is bottleneck
wsNode.jsRaw WebSocket server✅ Standard choice
Socket.IONode.jsReal-time with rooms/events✅ For real-time apps
uWebSockets.jsNode.jsUltra-high throughput⚠️ Complex, niche
Browser WebSocketBrowserClient-side✅ Native, no library needed
reconnecting-websocketBrowser/NodeAuto-reconnect client✅ Useful utility

S1 Recommendation: Rapid Discovery#

Preliminary Winners#

Use CaseLibraryLanguage
Standalone Python WS serverwebsocketsPython
WS in aiohttp appaiohttp WSPython
WS in FastAPI appstarlette WebSocketPython
Python Socket.IO serverpython-socketioPython
Node.js WS serverwsNode.js
Node.js real-time (rooms, events)socket.ioNode.js
Node.js extreme throughputuWebSockets.jsNode.js
Browser clientNative WebSocket APIBrowser
Browser auto-reconnectreconnecting-websocketBrowser/Node

Confidence#

High on websockets for Python (dominant, actively maintained). High on ws for Node.js (industry standard, 60M downloads/week). Medium on Socket.IO (strong but adds protocol complexity). Low on uWebSockets.js (high performance but complex, niche use).


S1 Synthesis: WebSocket Libraries#

Key Findings#

Python has three good options depending on your stack:

  • Standalone async server → websockets (purpose-built, clean API)
  • In an aiohttp app → aiohttp WebSocket (same process, same event loop)
  • In a FastAPI/Starlette app → Starlette WebSocket (ASGI, works with uvicorn)

The websockets library is the canonical Python WebSocket library. 25M+ downloads/month. Actively maintained. If you’re not already using aiohttp or FastAPI, use websockets.

Socket.IO ≠ WebSocket: Socket.IO is a higher-level protocol that runs over WebSocket. It adds rooms, events, and reconnect. If your client is a browser using Socket.IO, your server must speak Socket.IO too. python-socketio is the Python server for this.

Node.js: ws is the standard. 60M downloads/week. uWebSockets.js is only worth considering above ~10K concurrent connections or extreme throughput requirements — the API is complex and it’s a C++ binding.

Reverse proxy is required for production TLS: Neither websockets nor ws should handle TLS directly in production. nginx or Caddy terminate TLS and proxy WebSocket connections upstream. The Upgrade and Connection headers must be forwarded.

What S2 Should Investigate#

  • Binary frame handling in Python (bytes vs str)
  • Backpressure semantics: what happens when send queue fills?
  • Ping/pong keep-alive tuning for long-lived connections
  • Performance: websockets vs aiohttp vs Starlette at scale
  • Reconnect/backoff patterns for robust clients
  • nginx/Caddy WebSocket proxy configuration
S2: Comprehensive

S2 Approach: Comprehensive Analysis#

Objective: Deep-dive the technical details that matter for production WebSocket deployments.

Topics examined:

  1. Binary frame handling: Python bytes/str, Node.js Buffer vs string
  2. Backpressure and send queue management
  3. Ping/pong keep-alive: correct configuration for long-lived connections
  4. websockets library internals: connection lifecycle, concurrency model
  5. Starlette/FastAPI WebSocket ASGI model vs websockets
  6. Socket.IO protocol: what it adds and at what cost
  7. Performance comparison: websockets vs aiohttp vs Starlette at scale
  8. Reverse proxy configuration: nginx and Caddy for WebSocket
  9. Reconnect/backoff patterns: client-side robustness
  10. TLS in Python and Node.js WebSocket servers

S2 Comprehensive Analysis: WebSocket Libraries#

Research ID: 1.067 Pass: S2 — Comprehensive Analysis Date: 2026-02-17


1. WebSocket Protocol Deep Dive#

Frame Format (RFC 6455)#

A WebSocket frame consists of:

 0               1               2               3
 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len |    Extended payload length    |
|I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
|N|V|V|V|       |S|             |   (if payload len==126/127)   |
| |1|2|3|       |K|             |                               |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +

Opcodes:

  • 0x1 = Text frame (UTF-8)
  • 0x2 = Binary frame (arbitrary bytes)
  • 0x8 = Close
  • 0x9 = Ping
  • 0xA = Pong

Masking: Client→server frames MUST be masked (4-byte XOR mask). Server→client frames MUST NOT be masked.

Key implication: Text frames must be valid UTF-8. Binary frames have no encoding constraint. Sending Python bytes produces a binary frame; sending str produces a text frame.

HTTP Upgrade Handshake#

The Sec-WebSocket-Accept header is computed as:

import hashlib, base64
magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
accept = base64.b64encode(
    hashlib.sha1((key + magic).encode()).digest()
).decode()

This is a security measure to prevent cross-protocol attacks (a server that accidentally accepts WebSocket must have intentionally implemented the handshake).


2. Python websockets Library — Deep Dive#

Connection Lifecycle#

import websockets

async def handler(websocket):
    # websocket is a WebSocketServerProtocol
    # Iterating yields messages until connection closes
    try:
        async for message in websocket:
            # message is str (text frame) or bytes (binary frame)
            await websocket.send(message)  # echo
    except websockets.ConnectionClosed:
        pass  # normal close

websockets.serve() creates a TCP server. Each connection spawns the handler coroutine. The async for loop drives the receive loop.

Binary vs Text Frames#

# Text frame
await websocket.send("hello world")  # str → text frame (opcode 0x1)

# Binary frame
await websocket.send(b"\x00\x01\x02\x03")  # bytes → binary frame (opcode 0x2)

# Receiving:
message = await websocket.recv()
if isinstance(message, str):
    # text frame received
elif isinstance(message, bytes):
    # binary frame received

Backpressure: The write_limit Parameter#

websockets v10+ has write_limit (default 32768 bytes). When the send buffer exceeds this:

  • send() applies backpressure — it awaits until the buffer drains
  • Prevents unbounded memory growth when a slow client can’t keep up
websockets.serve(handler, "localhost", 8765, write_limit=65536)

Without backpressure, a fast server sending to a slow client will accumulate data in memory until OOM.

Ping/Pong Keep-Alive#

websockets.serve(
    handler,
    "localhost",
    8765,
    ping_interval=20,   # send ping every 20 seconds
    ping_timeout=20,    # close if no pong within 20 seconds
)

Default: ping_interval=20, ping_timeout=20. Adjust based on your proxy’s idle timeout (nginx default is 60s — set ping_interval < 60 to prevent proxy from closing idle connections).

Set ping_interval=None to disable — required if your app layer has its own heartbeat.

Broadcast Pattern#

websockets has no built-in room/broadcast abstraction. The standard pattern:

connected = set()

async def handler(websocket):
    connected.add(websocket)
    try:
        async for message in websocket:
            # broadcast to all connected clients
            websockets.broadcast(connected, message)
    finally:
        connected.discard(websocket)

websockets.broadcast() (v10+) is more efficient than asyncio.gather(*[ws.send(m) for ws in connected]) — it sends frames without waiting for acknowledgment.

Concurrency Model#

Each WebSocket connection runs in a single coroutine. Multiple connections = multiple concurrent coroutines in the same event loop. This is not multi-threaded — CPU-bound work blocks the event loop.

For CPU-bound per-message processing: use asyncio.get_event_loop().run_in_executor().


3. Starlette / FastAPI WebSocket — ASGI Model#

ASGI Interface#

Under the hood, Starlette’s WebSocket class wraps three ASGI callables:

  • scope: connection metadata (headers, path, query string)
  • receive: await the next ASGI event (WebSocket connect, receive, disconnect)
  • send: send ASGI event (WebSocket accept, send, close)
# Starlette WebSocket ASGI primitives
await websocket.accept()           # send 101
data = await websocket.receive_text()  # or receive_bytes(), receive_json()
await websocket.send_text(data)    # or send_bytes(), send_json()
await websocket.close()            # send close frame

FastAPI Dependency Injection with WebSocket#

from fastapi import FastAPI, WebSocket, Depends

app = FastAPI()

async def get_user(websocket: WebSocket):
    # Extract auth from query param or header
    token = websocket.query_params.get("token")
    return verify_token(token)

@app.websocket("/ws")
async def ws_endpoint(websocket: WebSocket, user=Depends(get_user)):
    await websocket.accept()
    while True:
        msg = await websocket.receive_text()
        await websocket.send_text(f"{user}: {msg}")

Performance vs websockets#

Starlette/FastAPI WebSocket adds overhead from the ASGI abstraction layer. For pure WebSocket throughput, websockets is faster. But for applications that also serve HTTP (REST + WebSocket in the same FastAPI app), Starlette’s WebSocket is the correct choice — one process, one server, unified routing.


4. Socket.IO Protocol#

What Socket.IO Adds#

Socket.IO is a framing protocol on top of WebSocket (and XHR polling fallback):

Engine.IO (transport layer): Manages the actual transport (WebSocket or polling). Handles:

  • Transport negotiation (starts with polling, upgrades to WebSocket)
  • Heartbeat/timeout
  • Binary support (custom encoding)

Socket.IO (application layer on top of Engine.IO):

  • Named events (not just “message”)
  • Acknowledgments (callback-style RPC)
  • Rooms (server-side groups for broadcasting)
  • Namespaces (virtual channels, like sub-applications)
// Server (Node.js)
io.on('connection', (socket) => {
    socket.join('room1');
    socket.on('chat', (data, callback) => {
        io.to('room1').emit('chat', data);
        callback({ received: true });  // acknowledgment
    });
});
# Server (Python)
import socketio
sio = socketio.AsyncServer()

@sio.event
async def chat(sid, data):
    await sio.emit('chat', data, room='room1')
    return {'received': True}  # acknowledgment

Socket.IO vs Raw WebSocket#

FeatureRaw WebSocketSocket.IO
Named events❌ (single message type)
Rooms/broadcastManual✅ built-in
ReconnectManual✅ automatic
AcknowledgmentsManual
Binary support✅ native✅ custom encoding
Polling fallback
Protocol overheadMinimal~20-50 bytes/message
Cross-language✅ any RFC 6455⚠️ Must use Socket.IO client

Key constraint: Socket.IO clients cannot connect to a raw WebSocket server and vice versa. The protocols are incompatible.

python-socketio: Modes#

import socketio
import aiohttp

# Async server (ASGI)
sio = socketio.AsyncServer(async_mode='aiohttp')
app = web.Application()
sio.attach(app)

# OR: as ASGI middleware (for Starlette/FastAPI)
sio = socketio.AsyncServer(async_mode='asgi')
app = socketio.ASGIApp(sio, other_asgi_app=fastapi_app)

5. Node.js: ws vs uWebSockets.js#

ws Performance Characteristics#

ws is pure JavaScript (no native modules). Performance at various concurrency levels:

  • 1,000 connections: ~100K messages/second
  • 10,000 connections: ~50-80K messages/second (OS limits become relevant)
  • Memory: ~2-5KB per connection

ws supports per-message deflate compression (permessage-deflate extension):

const wss = new WebSocket.Server({
    port: 8080,
    perMessageDeflate: {
        threshold: 1024,  // only compress messages > 1KB
        concurrencyLimit: 10
    }
});

uWebSockets.js Performance#

uWebSockets.js (µWS) is a C++ WebSocket library with Node.js bindings:

  • ~1M+ messages/second throughput on a single core
  • ~1KB memory per connection
  • Significantly faster than ws for high-concurrency workloads
const uWS = require('uWebSockets.js');

uWS.App().ws('/*', {
    message: (ws, message, isBinary) => {
        ws.send(message, isBinary);  // echo
    },
    open: (ws) => { console.log('connected'); },
    close: (ws, code, message) => { console.log('disconnected'); }
}).listen(9001, (token) => {
    if (token) console.log('Listening on port 9001');
});

Trade-offs vs ws:

  • Complex installation (native binary, may need rebuild)
  • Less ecosystem integration (doesn’t support all ws options)
  • Tighter API (no EventEmitter, different abstraction)
  • No Windows support in older versions

When to use uWebSockets: Above ~50K concurrent connections or when ws is a measured bottleneck.


6. Reverse Proxy: nginx WebSocket Configuration#

WebSocket traffic must pass through a properly configured reverse proxy. Two critical headers must be forwarded:

# /etc/nginx/sites-available/myapp
server {
    listen 80;
    server_name example.com;

    location /ws {
        proxy_pass http://127.0.0.1:8765;
        proxy_http_version 1.1;          # Required: WebSocket needs HTTP/1.1
        proxy_set_header Upgrade $http_upgrade;     # Forward Upgrade header
        proxy_set_header Connection "Upgrade";      # Force Connection: Upgrade
        proxy_set_header Host $host;
        proxy_read_timeout 3600s;        # Long timeout for idle WS connections
        proxy_send_timeout 3600s;
    }
}

Why proxy_http_version 1.1: HTTP/1.0 doesn’t support the Connection: Upgrade mechanism.

Why long timeouts: nginx default proxy_read_timeout is 60 seconds. An idle WebSocket connection will be killed unless either: (a) timeout is increased, or (b) ping/pong keep-alive is configured on the application.

Caddy equivalent:

example.com {
    reverse_proxy /ws localhost:8765
    # Caddy handles WebSocket upgrade automatically
}

Caddy auto-detects WebSocket upgrades and forwards them correctly — no special configuration needed.


7. Reconnect / Backoff Patterns#

Python Client (websockets)#

import asyncio
import websockets
from websockets.exceptions import ConnectionClosed

async def connect_with_backoff(uri: str):
    delay = 1
    max_delay = 60
    while True:
        try:
            async with websockets.connect(uri) as ws:
                delay = 1  # reset on successful connect
                async for message in ws:
                    await process(message)
        except (ConnectionClosed, OSError) as e:
            print(f"Disconnected: {e}. Reconnecting in {delay}s")
            await asyncio.sleep(delay)
            delay = min(delay * 2, max_delay)  # exponential backoff

JavaScript Client (reconnecting-websocket)#

import ReconnectingWebSocket from 'reconnecting-websocket';

const ws = new ReconnectingWebSocket('ws://localhost:8765', [], {
    maxRetries: 10,
    minReconnectionDelay: 1000,
    maxReconnectionDelay: 30000,
    reconnectionDelayGrowFactor: 2,
    connectionTimeout: 4000,
});

ws.onmessage = (event) => console.log(event.data);

Browser Native with Backoff#

function connectWithBackoff(url, onMessage) {
    let delay = 1000;
    function connect() {
        const ws = new WebSocket(url);
        ws.onmessage = onMessage;
        ws.onclose = () => {
            console.log(`Reconnecting in ${delay}ms`);
            setTimeout(() => {
                delay = Math.min(delay * 2, 30000);
                connect();
            }, delay);
        };
        ws.onopen = () => { delay = 1000; }; // reset
    }
    connect();
}

8. Key Technical Findings#

  1. websockets is the Python standard: v16.0 (Jan 2026), ~180M downloads/month, clean asyncio API. For any Python project not already on aiohttp/FastAPI, this is the default choice.

  2. Binary vs text matters: Python str → text frame (UTF-8 required); bytes → binary frame. Don’t send json.dumps(data).encode() expecting a text frame — that sends bytes → binary. Either keep as str or check isinstance(message, bytes) on receive.

  3. Backpressure is critical: write_limit prevents memory exhaustion when sending to slow clients. Set it appropriately for your use case.

  4. Ping/pong tuning: Set ping_interval < nginx proxy_read_timeout. Default nginx timeout is 60s; websockets default ping_interval is 20s — these defaults work correctly together.

  5. Socket.IO has lock-in: If you use Socket.IO on the server, you must use the Socket.IO client. This prevents third-party clients from connecting with the native WebSocket API.

  6. uWebSockets.js for extreme throughput only: The complexity cost is high. ws handles most production workloads.

  7. picows — the Python throughput alternative: picows is a Cython-based WebSocket library (~1.5-2x faster than websockets for high-throughput workloads). Pure RFC 6455, no asyncio overhead beyond the event loop. Use when websockets is a measured bottleneck but you want to stay in Python. Install: pip install picows. Note: less mature, smaller community than websockets.

  8. Caddy is simpler than nginx for WebSocket: Auto-detects upgrade, no extra configuration.


Sources#


S2 Recommendation: Comprehensive Analysis#

Core Decision: What’s Your Stack?#

Standalone Python async server → websockets Clean API, purpose-built, actively maintained. The default answer.

Inside an existing aiohttp app → aiohttp WebSocket No reason to add a second dependency. Same process, same event loop.

Inside a FastAPI app → Starlette WebSocket It’s already included. Integrates with dependency injection and routing.

Need rooms, events, reconnect → Socket.IO (python-socketio + socket.io client) Socket.IO protocol — clients must use Socket.IO client. Accept the lock-in or don’t.

Node.js → ws Standard. 60M downloads/week. Only reach for uWebSockets.js when ws is a measured bottleneck.

Production Checklist#

  • Set ping_interval < reverse proxy idle timeout (20s default is fine for nginx 60s timeout)
  • Set write_limit appropriate to your payload sizes (default 32KB is fine for typical use)
  • Use socks5h:// … wait, wrong survey. Use proxy_http_version 1.1 in nginx and forward Upgrade/Connection headers
  • Caddy handles WebSocket automatically — simpler than nginx for new deployments
  • Binary data: send Python bytes, not .encode() of JSON string if you need text frame

S2 Synthesis: WebSocket Libraries#

What S2 Confirmed from S1#

  • websockets is the canonical Python choice: clean asyncio API, actively maintained, 25M downloads/month
  • ws is the standard Node.js library: 60M downloads/week, minimal and fast
  • Socket.IO creates protocol lock-in — clients must use Socket.IO client; not compatible with raw WebSocket

What S2 Added#

Binary vs text frame semantics: Python str → text frame (opcode 0x1), bytes → binary frame (opcode 0x2). A common bug is sending json.dumps(data).encode() which produces a binary frame, not a text frame. Receive with isinstance(message, bytes) check.

Backpressure via write_limit: Without this, a fast server sending to a slow client accumulates unbounded memory. websockets v10+ write_limit=32768 default. Tune upward for large-payload use cases.

Ping/pong must be configured for production: nginx default proxy_read_timeout is 60s. Set ping_interval < 60 (websockets default 20s is correct). If using a different proxy or deploying without nginx, check the idle timeout of whatever sits in front.

Starlette WebSocket is the right choice inside FastAPI — not because it’s faster (it isn’t) but because it integrates with FastAPI’s dependency injection and routing. Mixing websockets library with FastAPI is possible but awkward.

uWebSockets.js is a performance tool, not a default: 10x throughput of ws, but C++ binding complexity, no Windows in older versions, different API. Only reach for it when ws is a measured bottleneck.

Caddy > nginx for WebSocket simplicity: Caddy auto-detects WebSocket upgrades. nginx requires proxy_http_version 1.1 and explicit Upgrade/Connection header forwarding.

Use CasePython ApproachQuality
Standalone Python WS serverwebsockets✅ Production
WS inside aiohttp appaiohttp WebSocket✅ Production
WS inside FastAPI appStarlette WebSocket✅ Production
Python Socket.IO serverpython-socketio✅ Production
Node.js WS serverws✅ Production
Node.js real-time frameworksocket.io✅ Production
Node.js extreme throughputuWebSockets.js⚠️ Complex
Auto-reconnect clientreconnecting-websocket✅ Production
S3: Need-Driven

S3 Approach: Need-Driven Discovery#

Objective: Work backward from real use cases to the correct library choice.

Topics examined:

  1. Chat application: rooms, broadcast, online presence
  2. Live dashboard / data feed: server-push, high-frequency updates
  3. Collaborative editor: operational transforms, conflict resolution, binary diff
  4. Game server: low latency, binary protocol, many concurrent connections
  5. IoT / device telemetry: reconnect robustness, binary frames, scaling
  6. Python backend + JS frontend: cross-language interop patterns
  7. Constraint matrix: no Node.js, serverless, behind corporate proxy

S3 Library Comparison: By Need Matrix#

Decision Matrix#

Needwebsocketsaiohttp WSStarlette WSpython-socketiows (Node)socket.io (Node)
Standalone Python server--
Existing aiohttp app⚠️ (separate)⚠️--
FastAPI/Starlette app⚠️ (separate)✅ ASGI--
Rooms / broadcast groupsManualManualManual✅ built-inManual✅ built-in
Auto-reconnect clientManualManualN/A✅ clientManual✅ client
Named events
Binary frames✅ bytes✅ Buffer
Polling fallback
Pure Python (no C ext)❌ (C ext)⚠️ (uvicorn)N/AN/A
Sync API✅ (v10+ sync)N/AN/A
Extreme throughput⚠️⚠️⚠️

When to Use Socket.IO vs Raw WebSocket#

Use Socket.IO when:

  • You need rooms/broadcast without writing the registry yourself
  • You need named events (not just raw message strings)
  • You need automatic reconnect with session resumption
  • Your clients are browsers that may be behind WebSocket-blocking proxies (polling fallback)
  • You’re building a classic chat/notification system

Use raw WebSocket when:

  • Clients are not browsers (IoT devices, other servers, mobile apps)
  • You control both client and server and don’t want the Socket.IO overhead
  • You need maximum performance (Socket.IO adds ~20-50 bytes overhead per message)
  • You’re building a binary protocol (game state, CRDT, telemetry)
  • You want standard RFC 6455 interoperability

WebSocket vs SSE vs Long-Polling#

TransportDirectionProtocolReconnectProxies
WebSocketBidirectionalRFC 6455 (Upgrade)Manual (library)Needs proxy config
SSEServer→ClientHTTP (EventSource)AutomaticWorks with all HTTP proxies
Long-pollingBidirectional (slow)HTTPAutomaticWorks everywhere

Choose SSE if: you only need server-to-client push (no client messages), or you’re behind a proxy that blocks WebSocket.

Choose WebSocket if: clients need to send data too, or you need low-latency bidirectional messaging.


S3 Recommendation: Need-Driven#

By Use Case#

Chat / real-time collaboration with rooms → Socket.IO (if Node.js) or manual room registry with websockets (if Python)

Live data feed / dashboard → websockets server-push or SSE if strictly one-way

Binary protocol (game, CRDT, telemetry) → websockets (Python) or ws (Node.js) — raw WebSocket, binary frames

IoT device backend → websockets with custom reconnect and device registry

FastAPI app + WebSocket → Starlette WebSocket built into FastAPI; no additional library

Cross-language Python+JS → websockets + browser native WebSocket — both speak RFC 6455, seamless

Behind corporate proxy / polling fallback → Socket.IO with transports: ['polling', 'websocket']

One-Line Rule#

If you need rooms/events/reconnect as features: Socket.IO. If you’re building a custom protocol: raw WebSocket (websockets for Python, ws for Node.js).


S3 Use Cases: WebSocket Libraries#

Use Case 1: Chat Application#

Who: Developer building a real-time chat with rooms and online presence.

Constraints:

  • Multiple rooms; broadcast to room members only
  • Online/offline presence
  • Python backend or Node.js backend

Python (websockets) approach:

import asyncio
import websockets
from collections import defaultdict

# Room registry: room_name → set of WebSocket connections
rooms: dict[str, set] = defaultdict(set)

async def handler(websocket, path):
    # path like /chat/room1
    room = path.strip('/').split('/')[-1]
    rooms[room].add(websocket)
    try:
        async for message in websocket:
            # broadcast to room
            websockets.broadcast(rooms[room], f"{room}: {message}")
    finally:
        rooms[room].discard(websocket)
        if not rooms[room]:
            del rooms[room]

Node.js (Socket.IO) approach — when rooms are a first-class need:

io.on('connection', (socket) => {
    socket.on('join', (room) => {
        socket.join(room);
        io.to(room).emit('presence', { user: socket.id, status: 'online' });
    });
    socket.on('message', (room, text) => {
        io.to(room).emit('message', { from: socket.id, text });
    });
    socket.on('disconnect', () => {
        // Socket.IO automatically removes from all rooms
    });
});

Verdict: For Python, implement rooms manually with websockets. For Node.js with rooms as a primary feature, Socket.IO’s built-in room support saves significant boilerplate.


Use Case 2: Live Dashboard (Server Push)#

Who: Developer pushing metrics or stock prices to a browser dashboard. Server sends; client rarely sends.

Constraints:

  • High-frequency updates (10-100 messages/second per connection)
  • Potentially many clients (100s-1000s)
  • Browser client

Python approach (websockets):

import asyncio
import json
import websockets

async def metrics_handler(websocket):
    # Server-push loop
    try:
        while True:
            data = await fetch_metrics()  # async DB/cache query
            await websocket.send(json.dumps(data))
            await asyncio.sleep(0.1)  # 10 Hz
    except websockets.ConnectionClosed:
        pass

Alternative: Server-Sent Events (SSE) — for pure server-push (no client messages needed), SSE is simpler:

  • Unidirectional: server → client only
  • Works over HTTP/1.1 without Upgrade
  • Automatic reconnect built into browser EventSource
  • Simpler proxy support (no special headers needed)

When to choose WebSocket over SSE: When the client also sends data (bidirectional). If it’s truly one-way push, consider SSE.

JavaScript client (handles backpressure):

const ws = new WebSocket('ws://localhost:8765');
let buffer = [];

ws.onmessage = (event) => {
    buffer.push(JSON.parse(event.data));
    // process in batches to avoid UI jank
    if (buffer.length >= 10) {
        updateDashboard(buffer.splice(0, 10));
    }
};

Use Case 3: Collaborative Editor (Binary Diff / CRDT)#

Who: Developer building a collaborative editor (like Google Docs) where multiple users edit simultaneously.

Constraints:

  • Binary data: operational transform diffs or CRDT state updates
  • Low latency (< 100ms round trip for feel of real-time)
  • Correctness: no data loss on concurrent edit

Python approach (binary frames):

import websockets

async def handler(websocket):
    async for message in websocket:
        if isinstance(message, bytes):
            # binary frame: CRDT operation payload
            op = decode_crdt_op(message)
            merged = apply_crdt_op(document_state, op)
            # broadcast binary update to all other clients
            websockets.broadcast(other_clients, encode_crdt_op(merged))
        else:
            # text frame: control messages (cursor position, etc.)
            ...

Takeaway: WebSocket’s binary frame support is ideal for CRDT libraries that use compact binary encoding (like yjs’s Y.js protocol, which uses binary encoding). Y.js (JavaScript) has a WebSocket provider (y-websocket) built on ws.


Use Case 4: Game Server (Low Latency, Binary Protocol)#

Who: Developer building a multiplayer game server. Every millisecond matters.

Constraints:

  • Many concurrent connections (1K-10K)
  • Binary protocol (game state as packed structs)
  • Strict latency requirements (< 50ms round trip)
  • Client is browser (WebSocket), server can be Python or Node.js

Node.js + uWebSockets.js (for maximum throughput):

const uWS = require('uWebSockets.js');

uWS.App().ws('/game', {
    compression: uWS.DISABLED,  // compression adds latency
    maxPayloadLength: 512,
    idleTimeout: 30,
    message: (ws, message, isBinary) => {
        if (isBinary) {
            const state = parseGameState(Buffer.from(message));
            ws.send(serializeUpdate(state), true);  // true = binary
        }
    }
}).listen(9001, () => console.log('Game server on 9001'));

Python option: For a Python game server, websockets works fine up to a few thousand connections. Above that, consider:

  • Running multiple Python processes behind a load balancer
  • Using uvicorn (Starlette) which is well-optimized for ASGI workloads

Latency note: Python’s GIL and event loop overhead means Node.js + ws will typically outperform Python for latency-sensitive workloads at high concurrency. For most game servers (< 1K concurrent), Python is fine.


Use Case 5: IoT / Device Telemetry#

Who: Developer receiving telemetry from embedded devices or IoT sensors over WebSocket.

Constraints:

  • Devices reconnect frequently (network issues, power cycles)
  • Binary protocol (compact sensor readings)
  • Backend must handle device authentication
  • Backend may need to push commands to devices

Python server (websockets with device auth):

import websockets
import json

async def device_handler(websocket, path):
    # Step 1: authenticate device
    try:
        auth_msg = await asyncio.wait_for(websocket.recv(), timeout=5.0)
        device_id = authenticate_device(json.loads(auth_msg))
    except (asyncio.TimeoutError, AuthError):
        await websocket.close(1008, "Unauthorized")
        return

    # Register device for command dispatch
    devices[device_id] = websocket
    try:
        async for message in websocket:
            if isinstance(message, bytes):
                reading = parse_sensor_frame(message)
                await store_reading(device_id, reading)
    finally:
        del devices[device_id]

# Send command to specific device
async def send_command(device_id: str, command: dict):
    if ws := devices.get(device_id):
        await ws.send(json.dumps(command))

Reconnect on device side (MicroPython example):

import uwebsocket
import time

delay = 1
while True:
    try:
        ws = uwebsocket.connect('ws://server:8765/device')
        ws.send(json.dumps({'type': 'auth', 'key': DEVICE_KEY}))
        while True:
            data = read_sensors()
            ws.send(pack_sensor_frame(data))
            time.sleep(1)
    except Exception:
        time.sleep(delay)
        delay = min(delay * 2, 60)

Use Case 6: Python Backend + JavaScript Frontend#

Cross-language interop: Python websockets server ↔ browser WebSocket client works seamlessly — both speak RFC 6455.

Pattern for JSON messages:

# Python server
await websocket.send(json.dumps({"type": "update", "data": payload}))
msg = json.loads(await websocket.recv())
// Browser client
ws.send(JSON.stringify({ type: "action", data: payload }));
ws.onmessage = (e) => {
    const msg = JSON.parse(e.data);
};

Pattern for binary messages (e.g., audio/video streaming, CRDT):

# Python: send binary
await websocket.send(bytes_data)  # binary frame
// Browser: receive as ArrayBuffer
ws.binaryType = 'arraybuffer';  // default is 'blob'
ws.onmessage = (e) => {
    const buffer = e.data;  // ArrayBuffer
    const view = new Uint8Array(buffer);
};

Socket.IO interop caveat: If you use Socket.IO in the browser (socket.io-client), you MUST use python-socketio on the server — not the raw websockets library. The protocols are incompatible.


Use Case 7: Constrained Environments#

Behind a Corporate Proxy (WebSocket Blocked)#

Many corporate HTTP proxies block the WebSocket Upgrade. Symptoms: connection fails at the upgrade step.

Options:

  1. Socket.IO with polling fallback: starts as HTTP long-polling, negotiates WebSocket if available
  2. WebSocket over HTTPS on port 443: harder for proxies to block (looks like HTTPS)
  3. Negotiate with network admin to whitelist wss:// endpoint

Socket.IO polling fallback:

const socket = io('https://server.com', {
    transports: ['polling', 'websocket']  // try polling first, upgrade if possible
});

Serverless (AWS Lambda, Cloudflare Workers)#

Standard WebSocket libraries don’t work in serverless environments (no persistent connections). Options:

  • AWS API Gateway WebSocket API: Managed WebSocket with connection IDs; Lambda handles events
  • Cloudflare Durable Objects: Stateful WebSocket support in Workers
  • Pusher / Ably: Managed real-time services (no server code needed)

For AWS API Gateway: the Python handler receives connectionId, routeKey, and body. No websockets library — use boto3 to send messages back via the Management API.

No Event Loop (Synchronous Python)#

The websockets library is async-only. For synchronous Python:

# websockets provides a sync wrapper (v10+)
from websockets.sync.client import connect

with connect("ws://localhost:8765") as ws:
    ws.send("hello")
    print(ws.recv())

This runs the event loop internally. Not suitable for use inside an async application, but convenient for scripts and tests.

S4: Strategic

S4 Strategic Analysis: WebSocket Libraries#

Research ID: 1.067 Pass: S4 — Strategic Analysis Date: 2026-02-17


1. Maintenance Trajectory#

websockets (Python)#

Maintainer: Aymeric Augustin (individual, principal maintainer since 2013). Also sponsored by companies using the library.

Stability: Extremely stable protocol target (RFC 6455 is from 2011, rarely updated). The library has no reason to change dramatically.

Risk: Single-maintainer dependency. If Augustin stops maintaining, the library could stall. However, the WebSocket protocol is stable enough that even a stale version would work for years.

Trajectory: v13+ (2024) added important features: sync client API, improved connection lifecycle, better backpressure. Development is active and thoughtful.

10-year confidence: HIGH — even in a worst case (maintainer retires), the library is stable enough to fork/maintain with minimal effort.

ws (Node.js)#

Maintainer: 3websocket (organization), multiple contributors.

GitHub stats: ~21K stars, ~147M weekly downloads. One of the most downloaded npm packages.

Trajectory: Mature and stable. v8.x has been the major version for several years. The API is unlikely to break.

10-year confidence: HIGH — industry standard.

Socket.IO#

Maintainer: Namespace (Damien Arrachequesne + contributors). Socket.IO Inc.

History of breaking changes: v2→v3 (2020) and v3→v4 (2021) had protocol-breaking changes. Cross-version clients/servers are incompatible.

Trajectory: v4.x appears stable. Active development continues.

Risk: Protocol version fragmentation. If you pin clients at Socket.IO v3 and upgrade server to v4, clients break. Requires coordinated upgrades.

10-year confidence: MEDIUM — the feature set is valuable but the history of breaking versions warrants caution.

python-socketio#

Maintainer: Miguel Grinberg (author of Flask-Migrate, Flask-Login, etc.). Individual.

Status: Actively maintained, follows Socket.IO v4 protocol.

Risk: Single maintainer. Dependent on Socket.IO protocol not breaking further.

10-year confidence: MEDIUM — solid now but concentration risk.


2. Protocol Evolution: WebTransport#

WebTransport is a W3C/IETF specification that provides multiplexed, bidirectional streams and unreliable datagrams over HTTP/3 (QUIC). It’s designed to complement or eventually replace WebSocket in browsers.

Current status (2026): WebTransport is in the W3C CR (Candidate Recommendation) phase. Chrome, Firefox, and Edge support it. Safari support is pending.

Advantages over WebSocket:

  • Multiple streams: A single WebTransport connection can have independent streams without head-of-line blocking (WebSocket has one stream)
  • Unreliable datagrams: UDP-like delivery for game state, live video
  • HTTP/3 foundation: Runs over QUIC (no TCP head-of-line blocking)
  • Connection migration: Client IP change doesn’t break the connection

Current Python support: Experimental. aioquic (by the Chromium team) includes WebTransport server support but it’s not production-ready.

Implication for WebSocket: WebTransport won’t kill WebSocket in the near term (2-5 years). WebSocket is ubiquitous, implemented everywhere, and WebTransport’s adoption is just beginning. Long-term (5-10 years), WebTransport may become the preferred transport for high-performance browser applications.

Recommendation: Build on WebSocket now. WebTransport is worth watching but not worth building on for production applications in 2026.


3. HTTP/2 and WebSocket#

WebSocket is defined for HTTP/1.1 (Upgrade mechanism). HTTP/2 does have a WebSocket extension (RFC 8441), but:

  • It’s rarely used in practice
  • Most WebSocket servers still use HTTP/1.1 for the upgrade
  • nginx proxies WebSocket over HTTP/1.1 even when the frontend uses HTTP/2

This is a non-issue for most applications. WebSocket over HTTP/1.1 works fine even in HTTP/2 environments (the WS connection just uses the HTTP/1.1 upgrade path on the same port).


4. Lock-in Analysis#

Switching websockets → aiohttp WS#

Low cost. Both use asyncio. The handler API is slightly different but the logic is the same.

Switching websockets → Starlette WS#

Medium cost. Need to restructure around ASGI request handling. The send/receive API is different.

Switching raw WebSocket → Socket.IO#

High cost. Clients must change from native WebSocket to Socket.IO client. This is a breaking change for all consumers.

Switching Socket.IO → raw WebSocket#

High cost. Same reason — all clients must change. Also lose rooms/events/reconnect built-ins.

Switching ws → uWebSockets.js#

Medium cost. Different API, requires testing under load. Internal change only (server-side).

Recommendation: Choose raw WebSocket or Socket.IO based on your actual needs, not “maybe I’ll need Socket.IO features later.” The migration cost is high in both directions.


5. Key Risks Summary#

LibraryPrimary RiskSeverity
websocketsSingle maintainerLow (protocol stable, easy to maintain)
wsNone significantVery low
Socket.IOProtocol version fragmentationMedium
python-socketioSingle maintainerMedium
uWebSockets.jsNative binary, platform issuesMedium
WebTransportImmature, browser support incompleteHigh (don’t use in production yet)

6. Strategic Summary#

The safe default for Python (2026 and beyond): websockets. Protocol is stable, library is actively maintained, clean asyncio API. Even if maintenance slows, the RFC 6455 spec won’t change — any forks would be straightforward.

The safe default for Node.js: ws. Industry standard. If Socket.IO features are needed, add Socket.IO (which uses ws internally anyway).

Socket.IO: A sound choice if you specifically need its features (rooms, events, reconnect, polling fallback). Accept the version coordination requirement. Don’t use it “just in case” — switching back to raw WebSocket later is expensive.

WebTransport: Watch for 2027+. Not ready for production today.


S4 Approach: Strategic Analysis#

Objective: Assess long-term viability, ecosystem health, and strategic risks.

Topics examined:

  1. Maintenance trajectory: who maintains these libraries and for how long?
  2. Protocol evolution: HTTP/2 WebSocket, HTTP/3 WebTransport
  3. WebTransport as a potential successor
  4. Socket.IO version fragmentation risk
  5. Lock-in analysis: switching costs
  6. Final recommendations with confidence levels

S4 Recommendation: Strategic Analysis#

Final Library Slots#

Use CaseLibraryStrategic Confidence
Python async WS serverwebsockets⭐⭐⭐⭐⭐ Permanent
Python in aiohttp appaiohttp WS⭐⭐⭐⭐ Stable
Python in FastAPI appStarlette WS⭐⭐⭐⭐ Stable
Python Socket.IO serverpython-socketio⭐⭐⭐⭐ Watch maintainer
Node.js WS serverws⭐⭐⭐⭐⭐ Permanent
Node.js real-timesocket.io v4⭐⭐⭐⭐ Pin version
Extreme throughputuWebSockets.js⭐⭐⭐ When measured
Future transportWebTransport⭐⭐ 2027+ only

Long-Term Outlook#

WebSocket (RFC 6455) will remain the dominant bidirectional browser transport for 5+ years. WebTransport is the eventual successor but is too early for production.

websockets and ws are the permanent answers for Python and Node.js respectively. Socket.IO remains a sound choice for its specific feature set — just pin the version and coordinate upgrades.


Strategic Viability: WebSocket Libraries#

Long-Term Slots#

Library2-Year5-YearRole
websockets (Python)⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐Python WebSocket standard
ws (Node.js)⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐Node.js WebSocket standard
Socket.IO⭐⭐⭐⭐⭐⭐⭐⭐Real-time framework (when needed)
python-socketio⭐⭐⭐⭐⭐⭐⭐Python Socket.IO (watch maintainer)
picows (Python)⭐⭐⭐⭐⭐⭐Python throughput alternative (Cython)
uWebSockets.js⭐⭐⭐⭐⭐⭐Extreme throughput only
WebTransport⭐⭐⭐⭐⭐⭐Future; not production yet

What to Build On#

Build on: websockets, ws, socket.io (v4).

Use carefully: python-socketio (sound but watch maintainer health), uWebSockets.js (only when needed).

Don’t build on: WebTransport (too early), any WebSocket library that hasn’t been updated in 2+ years.

Published: 2026-03-06 Updated: 2026-03-06