Skip to content

WebSocket Architecture

The real-time surface of dichit-backend is a single authenticated WebSocket endpoint that serves every role. This page describes the server-side architecture; the client-facing contract is in api/websocket/README.md.

Components

flowchart TB
    C1["Client (mobile/web)"]
    C2["Client 2"]
    C3["Client 3"]

    subgraph WS[WebSocket layer - src/interfaces/websocket]
        R["WebSocketRouter (ws /ws)"]
        H["Handlers<br/>system / room / auction"]
        M["Middleware (auth, rate limit, validation)"]
        SC["Schemas (Zod)"]
    end

    subgraph CORE[Real-time core - src/infrastructure/websocket]
        B["Broadcaster"]
        PS["Pub/Sub adapter (Redis)"]
    end

    subgraph DOM["Domain"]
        SVC["AuctionRealtimeService"]
        PRES["AuctionPresenceService"]
    end

    C1 & C2 & C3 -->|"wss://…/ws (JWT)"| R
    R --> M
    R --> H
    H --> SVC
    SVC --> PRES
    SVC --> B
    B --> PS

    subgraph Redis
        CH["ws:global channel"]
    end
    PS <--> CH

How messages flow

sequenceDiagram
    participant C as Client
    participant R as Router (interfaces)
    participant H as Handler
    participant S as Realtime service
    participant B as Broadcaster
    participant PS as Redis Pub/Sub
    participant DB as PostgreSQL

    C->>R: { type, requestId, data }
    R->>R: validate (Zod schema per event)
    R->>R: authz + per-event rate limit
    R->>H: route to registered handler
    H->>S: apply business logic
    S->>DB: persist / read state
    S->>B: publish update
    B->>PS: publish to ws:global
    PS-->>B: fan-out (all instances)
    B-->>C: server event (bid.created, presence.changed…)

Key point: any instance can deliver to any client. Room membership is per-connection, and fan-out is done through Redis so horizontal scaling does not require sticky sessions or a broker on the socket layer.

Key facts

  • Endpoint: GET /ws (@fastify/websocket), registered from src/interfaces/websocket/index.ts.
  • Every connection authenticates during the handshake with an access-token JWT (header or bearer.<token> subprotocol).
  • Message envelope, event names, rooms, and errors are defined in api/websocket/protocol.md, api/websocket/rooms.md, and api/websocket/errors.md.
  • Broadcasts traverse Redis (channel ws:global); rooms are logical, not physical.
  • Implementation walkthrough: infrastructure/websocket.md.

Scaling

flowchart LR
    LB["Load balancer / ALB"]
    W1["WS instance 1"]
    W2["WS instance 2"]
    R[("Redis Pub/Sub")]
    DB[(PostgreSQL)]
    LB --> W1 & W2
    W1 <--> R
    W2 <--> R
    W1 --> DB
    W2 --> DB