WebSocket
WebSocket provides bidirectional, full-duplex communication over a single TCP connection. It's the most widely supported real-time protocol.
Try it Online
Open WebSocket Client — See WebSocket streaming in action!
Overview
| Property | Value |
|---|---|
| Protocol | TCP (WebSocket) |
| Direction | Bidirectional |
| Latency | 1-5ms |
| Reconnection | Manual |
| Browser Support | Universal |
Endpoints
| Endpoint | Description |
|---|---|
ws://host/ws/:game | Stream by game slug |
ws://host/ws/type/:type | Stream by game type |
Examples
ws://localhost:3000/ws/crash
ws://localhost:3000/ws/double
ws://localhost:3000/ws/type/multiplier
wss://datastream.andrebassi.com.br/ws/crash
Message Format
Incoming Messages (Server → Client)
{
"round_id": 512,
"game_id": 1,
"game_slug": "crash",
"game_type": "multiplier",
"finished_at": "2026-01-17T00:45:42.735266-03:00",
"extras": "{\"point\": \"11.72\"}",
"timestamp": 1768621542123
}
Field Descriptions
| Field | Type | Description |
|---|---|---|
round_id | integer | Unique round identifier |
game_id | integer | Game ID in database |
game_slug | string | URL-friendly game name |
game_type | string | Game category |
finished_at | string (ISO 8601) | Round completion time |
extras | string (JSON) | Game-specific data |
timestamp | integer | Unix timestamp in milliseconds |
Client Implementation
JavaScript (Browser)
class WebSocketClient {
constructor(game) {
this.game = game;
this.ws = null;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
this.reconnectDelay = 3000;
}
connect() {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const url = `${protocol}//${window.location.host}/ws/${this.game}`;
this.ws = new WebSocket(url);
this.ws.onopen = () => {
console.log('WebSocket connected');
this.reconnectAttempts = 0;
};
this.ws.onmessage = (event) => {
const data = JSON.parse(event.data);
this.handleMessage(data);
};
this.ws.onclose = () => {
console.log('WebSocket disconnected');
this.reconnect();
};
this.ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
}
handleMessage(data) {
console.log('New round:', data.round_id);
console.log('Result:', JSON.parse(data.extras));
}
reconnect() {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
console.error('Max reconnect attempts reached');
return;
}
this.reconnectAttempts++;
console.log(`Reconnecting in ${this.reconnectDelay}ms...`);
setTimeout(() => {
this.connect();
}, this.reconnectDelay);
}
disconnect() {
if (this.ws) {
this.ws.close();
}
}
}
// Usage
const client = new WebSocketClient('crash');
client.connect();
Go Client
package main
import (
"encoding/json"
"log"
"time"
"github.com/gorilla/websocket"
)
type Round struct {
RoundID int64 `json:"round_id"`
GameSlug string `json:"game_slug"`
GameType string `json:"game_type"`
FinishedAt time.Time `json:"finished_at"`
Extras json.RawMessage `json:"extras"`
Timestamp int64 `json:"timestamp"`
}
func main() {
url := "ws://localhost:3000/ws/crash"
c, _, err := websocket.DefaultDialer.Dial(url, nil)
if err != nil {
log.Fatal("dial:", err)
}
defer c.Close()
for {
_, message, err := c.ReadMessage()
if err != nil {
log.Println("read:", err)
return
}
var round Round
if err := json.Unmarshal(message, &round); err != nil {
log.Println("unmarshal:", err)
continue
}
log.Printf("Round %d: %s", round.RoundID, round.Extras)
}
}
Python Client
import asyncio
import json
import websockets
async def connect():
uri = "ws://localhost:3000/ws/crash"
async with websockets.connect(uri) as websocket:
print("Connected to WebSocket")
async for message in websocket:
data = json.loads(message)
print(f"Round {data['round_id']}: {data['extras']}")
if __name__ == "__main__":
asyncio.run(connect())
Server Implementation
The WebSocket handler is implemented using Fiber's WebSocket middleware:
// internal/adapters/inbound/websocket/handlers.go
func (h *Handlers) StreamByGame(c *websocket.Conn) {
game := c.Params("game")
ctx := context.Background()
// Get last result and send immediately
if latest, err := h.roundRepo.GetLatest(ctx, game); err == nil {
data, _ := json.Marshal(latest)
c.WriteMessage(websocket.TextMessage, data)
}
// Subscribe to Redis Pub/Sub
channel := fmt.Sprintf("stream:%s", game)
sub, err := h.subscriber.Subscribe(ctx, channel)
if err != nil {
return
}
// Stream messages
for round := range sub {
data, _ := json.Marshal(round)
if err := c.WriteMessage(websocket.TextMessage, data); err != nil {
return
}
}
}
Best Practices
Connection Management
- Always implement reconnection logic - WebSocket doesn't auto-reconnect
- Use exponential backoff - Prevent server overload during outages
- Handle connection errors gracefully - Show user-friendly messages
- Close connections properly - Call
ws.close()when done
Message Handling
- Parse JSON safely - Handle malformed messages
- Validate message structure - Check required fields
- Process messages asynchronously - Don't block the message loop
- Buffer messages if needed - Queue for UI updates
Security
- Use WSS (WebSocket Secure) in production
- Validate origin header - Prevent cross-site attacks
- Implement rate limiting - Protect against abuse
- Authenticate connections - Use tokens in query string or first message
Troubleshooting
Connection Refused
WebSocket connection to 'ws://localhost:3000/ws/crash' failed
Solutions:
- Check if backend is running
- Verify the port is correct
- Check firewall settings
Connection Closes Immediately
WebSocket closed with code 1006
Solutions:
- Check server logs for errors
- Verify game slug exists
- Check Redis connection
Messages Not Received
Solutions:
- Verify Redis Pub/Sub is working
- Check consumer is running
- Ensure CDC pipeline is active
Performance Issues
Solutions:
- Implement message batching
- Use binary protocol for high volume
- Consider connection pooling
Comparison with Other Protocols
| Feature | WebSocket | SSE | WebTransport |
|---|---|---|---|
| Bidirectional | Yes | No | Yes |
| Auto-reconnect | No | Yes | No |
| Binary data | Yes | No | Yes |
| Multiple streams | No | No | Yes |
| Proxy-friendly | Sometimes | Yes | No |
When to Use WebSocket
Use WebSocket when:
- Client needs to send data to server
- Latency < 50ms is critical
- Many small messages per second
- Long-lived connections
Consider alternatives when:
- Server-only push (use SSE)
- Ultra-low latency needed (use WebTransport)
- Behind restrictive proxies (use SSE)