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 fromsrc/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 Related¶
- api/websocket/README.md — the contract
- infrastructure/websocket.md — implementation
- infrastructure/redis.md — pub/sub + caching
- event-driven.md
- runbooks/websocket-down.md