Protocol Comparison
Choose the right streaming protocol for your use case.
Compare Live
Open Comparison Client — See all protocols side-by-side in real-time!
Quick Comparison
| Feature | NATS WS | WebSocket | WebTransport | SSE | REST API |
|---|---|---|---|---|---|
| Direction | Bidirectional | Bidirectional | Bidirectional | Server → Client | Request/Response |
| Protocol | WebSocket | TCP | WS + QUIC (via Centrifugo) | HTTP | HTTP |
| Latency | <1ms | 1-5ms | ~10ms | 10-50ms | 50-200ms |
| Overhead | ~20 bytes | 2-14 bytes | ~30 bytes | ~50 bytes | ~200 bytes |
| Reconnection | Configurable | Manual | Automatic | Automatic | N/A |
| Multiple streams | Via subjects | No | Via channels | No | N/A |
| Wildcards | Yes | No | No | No | No |
| Direct broker | Yes | No | No | No | No |
| Browser support | ES Modules | Universal | Universal (fallback) | IE except | Universal |
| Proxy-friendly | Sometimes | Sometimes | Yes (via Centrifugo) | Always | Always |
Detailed Analysis
Connection Establishment
Message Overhead
| Protocol | Frame Format | Overhead |
|---|---|---|
| WebTransport | [Length] + Payload | 2-6 bytes |
| NATS WS | [Subject][Payload] | ~20 bytes |
| WebSocket | [FIN][RSV][Opcode][Mask][Len] + Payload | 2-14 bytes |
| SSE | event: message\ndata: {...}\n\n | ~50 bytes |
| REST API | HTTP Headers + Body | ~200 bytes |
Use Case Matrix
| Use Case | Recommended | Why |
|---|---|---|
| Dashboard | SSE | Auto-reconnect, simple |
| Chat | WebSocket | Bidirectional needed |
| Gaming | NATS | Ultra-low latency, direct broker |
| Notifications | SSE | Server push only |
| Trading | NATS | Lowest latency critical |
| Live scores | SSE | Simple, reliable |
| Multi-game monitoring | NATS | Wildcard subscriptions |
| Collaborative editing | WebSocket | Bidirectional, reliable |
| Scalable real-time | WebTransport | Horizontal scaling, auto-reconnect |
| History queries | REST API | Request/response, caching |
| Polling fallback | REST API | Universal compatibility |
Decision Tree
Performance Benchmarks
Latency (ms)
| Protocol | P50 | P95 | P99 |
|---|---|---|---|
| NATS WS | 0.9 | 1.8 | 2.8 |
| WebSocket | 2.1 | 4.8 | 8.2 |
| WebTransport (Centrifugo) | 8.0 | 15.0 | 22.0 |
| SSE | 15.3 | 38.2 | 62.1 |
| REST API | 65.0 | 120.0 | 180.0 |
Throughput (messages/second)
| Protocol | Single Connection | 1000 Connections |
|---|---|---|
| NATS WS | 70,000 | 65,000 |
| WebSocket | 50,000 | 45,000 |
| WebTransport (Centrifugo) | 60,000 | 55,000 |
| SSE | 20,000 | 18,000 |
| REST API | 100,000 | 90,000 |
Memory Usage (per connection)
| Protocol | Idle | Active |
|---|---|---|
| SSE | 4 KB | 8 KB |
| WebSocket | 8 KB | 16 KB |
| NATS WS | 10 KB | 18 KB |
| WebTransport (Centrifugo) | 12 KB | 20 KB |
Browser Compatibility
WebSocket
| Browser | Version | Support |
|---|---|---|
| Chrome | 4+ | ✅ |
| Firefox | 11+ | ✅ |
| Safari | 5+ | ✅ |
| Edge | 12+ | ✅ |
| IE | 10+ | ✅ |
| iOS Safari | 4.2+ | ✅ |
| Android | 4.4+ | ✅ |
SSE
| Browser | Version | Support |
|---|---|---|
| Chrome | 6+ | ✅ |
| Firefox | 6+ | ✅ |
| Safari | 5+ | ✅ |
| Edge | 79+ | ✅ |
| IE | - | ❌ |
| iOS Safari | 5+ | ✅ |
| Android | 4.4+ | ✅ |
WebTransport
| Browser | Version | Support |
|---|---|---|
| Chrome | 97+ | ✅ |
| Firefox | 114+ | ✅ |
| Safari | - | ❌ |
| Edge | 97+ | ✅ |
| IE | - | ❌ |
| iOS Safari | - | ❌ |
| Android Chrome | 97+ | ✅ |
Infrastructure Considerations
| Protocol | Pros | Cons |
|---|---|---|
| WebTransport | Best performance, multiple streams, unreliable delivery | Requires UDP, limited proxy/CDN support |
| NATS WS | Direct broker, wildcards, low latency | Requires NATS port exposure, ES modules |
| WebSocket | Works with most LBs, nginx/HAProxy/Cloudflare support | May need sticky sessions, proxy timeout issues |
| SSE | Standard HTTP everywhere, no special config, CDN-friendly | 6 conn/domain (HTTP/1.1), unidirectional |
| REST API | Universal, cacheable, stateless | Polling overhead, higher latency |
Code Examples
Unified Client
class StreamClient {
constructor(game, options = {}) {
this.game = game;
this.protocol = options.protocol || 'auto';
this.onMessage = options.onMessage || console.log;
this.connection = null;
}
async connect() {
switch (this.protocol) {
case 'websocket':
return this.connectWebSocket();
case 'sse':
return this.connectSSE();
case 'webtransport':
return this.connectWebTransport();
case 'auto':
default:
return this.connectAuto();
}
}
async connectAuto() {
// Try WebTransport first (best performance)
if (typeof WebTransport !== 'undefined') {
try {
await this.connectWebTransport();
return;
} catch (e) {
console.log('WebTransport unavailable, trying WebSocket');
}
}
// Fallback to WebSocket
try {
await this.connectWebSocket();
} catch (e) {
console.log('WebSocket unavailable, trying SSE');
await this.connectSSE();
}
}
connectWebSocket() {
return new Promise((resolve, reject) => {
const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
const ws = new WebSocket(`${proto}//${location.host}/ws/${this.game}`);
ws.onopen = () => {
this.connection = ws;
resolve();
};
ws.onmessage = (e) => {
this.onMessage(JSON.parse(e.data));
};
ws.onerror = reject;
});
}
connectSSE() {
return new Promise((resolve) => {
const sse = new EventSource(`/sse/${this.game}`);
this.connection = sse;
sse.onopen = resolve;
sse.addEventListener('message', (e) => {
this.onMessage(JSON.parse(e.data));
});
sse.addEventListener('initial', (e) => {
this.onMessage(JSON.parse(e.data));
});
});
}
async connectWebTransport() {
// WebTransport via Centrifugo with automatic fallback
const transports = [];
if (typeof WebTransport !== 'undefined') {
transports.push({
transport: 'webtransport',
endpoint: 'https://wt.datastream.hypetech.games/connection/webtransport'
});
}
transports.push({
transport: 'websocket',
endpoint: 'wss://wt.datastream.hypetech.games/connection/websocket'
});
const centrifuge = new Centrifuge(transports);
centrifuge.on('connected', () => {
const channel = `stream:${this.game}`;
const sub = centrifuge.newSubscription(channel);
sub.on('publication', (ctx) => this.onMessage(ctx.data));
sub.subscribe();
});
centrifuge.connect();
this.connection = centrifuge;
}
disconnect() {
if (this.connection) {
if (this.connection.close) {
this.connection.close();
}
}
}
}
// Usage
const client = new StreamClient('crash', {
protocol: 'auto',
onMessage: (data) => {
console.log(`Round ${data.round_id}: ${data.extras}`);
}
});
await client.connect();
Recommendations
For Most Applications
Use SSE as the default choice:
- Simplest to implement
- Best proxy compatibility
- Auto-reconnection
- Sufficient for most real-time needs
For Interactive Applications
Use WebSocket when:
- Client needs to send data
- Bidirectional communication required
- Gaming, chat, collaboration
For Direct Broker Access
Use NATS WebSocket when:
- Need wildcard subscriptions (e.g., all games)
- Want lowest latency without backend hop
- Building internal dashboards
- Have NATS port accessible to clients
For Scalable Real-time
Use WebTransport (via Centrifugo) when:
- You need horizontal scaling via Redis
- You want automatic reconnection
- You need universal browser support (automatic WebSocket fallback)
- You want managed real-time infrastructure
For Universal Compatibility
Use REST API when:
- Need historical data
- Building mobile apps
- Caching is important
- Real-time not required
- Fallback for streaming protocols