Membangun Sistem Reactive Real-Time dengan Vue.js 3 Composition API dan WebSockets
1. Konteks Industri & Analisis Akar Masalah
Dalam lanskap e-commerce modern, ekspektasi pengguna terhadap kecepatan dan ketersediaan data secara instan telah mencapai tingkat yang belum pernah terjadi sebelumnya. Dasbor inventaris, pembaruan flash sale, penawaran harga dinamis, dan pelacakan pengiriman kurir berbasis peta memerlukan latensi mendekati nol. Pendekatan tradisional seperti Short Polling atau Long Polling menggunakan HTTP/1.1 REST API terbukti menjadi beban yang sangat berat bagi infrastruktur backend dan memboroskan bandwidth, terutama saat menghadapi lonjakan concurrent connections.
Secara arsitektural, penggunaan HTTP Polling berulang kali membuka dan menutup koneksi TCP, memicu handshake TLS yang mahal, serta membebani server web (seperti Nginx atau Caddy) dan proses aplikasi backend (seperti Node.js, Go, atau PHP-FPM) dengan jutaan permintaan kosong (empty requests) yang hanya mendapati status 304 Not Modified. Hal ini menyebabkan:
- Resource Starvation: Penggunaan CPU dan RAM yang melonjak pada load balancer dan server aplikasi.
- Network Overhead: Header HTTP berukuran 500-800 bytes dikirim berulang kali untuk payload data real-time yang sebenarnya sangat kecil.
- Poor UX: Keterlambatan (delay) pembaruan data yang bergantung pada interval polling (misalnya setiap 5 detik), mengakibatkan overselling saat flash sale.
Sebelum Penerapan (HTTP Polling):
- Klien melakukan
GET /api/inventorysetiap 3 detik. - 10.000 pengguna aktif menghasilkan 3.333 request per detik, membuat koneksi database jenuh (connection pool exhaustion).
Sesudah Penerapan (WebSocket + Vue 3 Composition API):
- Satu koneksi TCP persisten per klien menggunakan protokol WebSocket.
- Server hanya mendorong delta data (payload minimal) saat terjadi perubahan inventaris.
- Beban CPU turun drastis, latensi komunikasi berkurang dari hitungan detik menjadi milidetik, dan pengalaman pengguna menjadi sangat responsif.
2. Konsep Inti & Deep Dive Arsitektur
Arsitektur real-time berbasis WebSocket memerlukan sinkronisasi ketat antara lapisan infrastruktur jaringan, backend message broker, dan frontend state management.
Alur Komunikasi Arsitektur Sistem
+-------------+ WebSocket (TCP) +---------------+ Redis Pub/Sub +------------------+
| Vue.js 3 | <=======================> | Node.js / Go | <=====================> | Backend +
| Composition | | WebSocket Hub | | Workers / Cron |
| API | +---------------+ +------------------+
+-------------+ |
Reverse Proxy
(Nginx / Cloudflare)
|
+---------------+
| Load Balancer|
+---------------+
Di balik layar, komunikasi dimulai dengan HTTP Handshake standar (permintaan Upgrade: websocket), yang kemudian mengubah koneksi HTTP tersebut menjadi saluran komunikasi full-duplex berbasis TCP. Karena infrastruktur produksi sering kali melewati Nginx, Load Balancer, dan CDN (seperti Cloudflare), konfigurasi proxy_pass dan timeout harus disesuaikan secara presisi agar koneksi tidak terputus secara sepihak (idle timeout).
3. Perbandingan Komparatif / Analisis Pendekatan
| Parameter Komparasi | HTTP Short Polling | Server-Sent Events (SSE) | WebSocket (Bi-directional) |
|---|---|---|---|
| Arah Komunikasi | Unidirectional (Client -> Server) | Unidirectional (Server -> Client) | Full-Duplex (Client <-> Server) |
| Protokol Lapisan | HTTP/1.1 atau HTTP/2 | HTTP/1.1 atau HTTP/2 | TCP (Custom WebSocket Framing) |
| Overhead Header | Tinggi (Setiap request) | Rendah (Sekali buka koneksi) | Sangat Rendah (Setelah handshake) |
| Konsumsi CPU/RAM | Sangat Tinggi (Beban koneksi baru) | Rendah | Sedang (Memelihara koneksi persisten) |
| Dukungan Fallback | N/A | Otomatis via Browser | Membutuhkan Logika Kustom |
| Kasus Penggunaan | Dashboard statis sederhana | Notifikasi berita, stock ticker | E-commerce flash sale, chat interaktif |
4. Prasyarat Sistem & Persiapan Lingkungan
Sebelum masuk ke implementasi kode, pastikan infrastruktur sistem memenuhi spesifikasi berikut:
- Node.js: Versi 18.x LTS atau lebih tinggi (mendukung fetch global dan native WebSocket di beberapa modul).
- Nginx: Versi 1.20+ dengan modul proxy konfigurasi Upgrade header.
- Redis: Versi 7.x untuk manajemen Pub/Sub dan sinkronisasi antar instance WebSocket server.
- Vue.js 3: Proyek yang dikonfigurasi menggunakan Vite dan Composition API (
<script setup>).
5. Panduan Implementasi Langkah demi Langkah
A. Konfigurasi Nginx untuk WebSocket Reverse Proxy
Buat atau perbarui file konfigurasi server block Nginx Anda (/etc/nginx/sites-available/ecommerce-ws.conf):
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
upstream websocket_backend {
server 127.0.0.1:8080;
keepalive 32;
}
server {
listen 80;
server_name api.ecommerce-realtime.internal;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name api.ecommerce-realtime.internal;
ssl_certificate /etc/letsencrypt/live/api.ecommerce-realtime.internal/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.ecommerce-realtime.internal/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
location /ws {
proxy_pass http://websocket_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Timeout configurations untuk koneksi long-live WebSocket
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_connect_timeout 60s;
}
}
B. Implementasi Backend WebSocket Server (Node.js + ws + Redis)
Inisialisasi direktori server dan instal dependensi:
mkdir ws-server && cd ws-server
npm init -y
npm install ws ioredis dotenv
Buat file server.js:
import { WebSocketServer } from 'ws';
import Redis from 'ioredis';
import dotenv from 'dotenv';
dotenv.config();
const PORT = process.env.PORT || 8080;
const wss = new WebSocketServer({ port: PORT });
const redisSub = new Redis(process.env.REDIS_URL || 'redis://localhost:6379');
// Subscribe ke Redis channel untuk broadcast lintas cluster
redisSub.subscribe('inventory-updates', (err, count) => {
if (err) {
console.error('Failed to subscribe to Redis channel:', err);
} else {
console.log(`Subscribed to Redis. Active channels: ${count}`);
}
});
redisSub.on('message', (channel, message) => {
if (channel === 'inventory-updates') {
// Broadcast ke semua client Vue.js yang terhubung
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(message);
}
});
}
});
wss.on('connection', (ws, req) => {
console.log(`Client connected from IP: ${req.socket.remoteAddress}`);
ws.on('message', (data) => {
try {
const parsed = JSON.parse(data);
if (parsed.action === 'ping') {
ws.send(JSON.stringify({ action: 'pong', timestamp: Date.now() }));
}
} catch (e) {
console.error('Invalid JSON received:', data);
}
});
ws.on('close', () => {
console.log('Client disconnected');
});
ws.on('error', (error) => {
console.error('WebSocket error:', error);
});
});
console.log(`WebSocket Server running on port ${PORT}`);
C. Implementasi Vue.js 3 Composable (Composition API)
Buat file composable useWebSocket.js di dalam proyek Vue 3 Anda untuk mengelola koneksi, state, dan mekanisme rekoneksi otomatis secara modular:
import { ref, onMounted, onUnmounted } from 'vue';
export function useWebSocket(url) {
const socket = ref(null);
const isConnected = ref(false);
const lastMessage = ref(null);
const error = ref(null);
let reconnectAttempts = 0;
let maxReconnectAttempts = 10;
let reconnectInterval = 3000;
let heartbeatTimer = null;
const connect = () => {
socket.value = new WebSocket(url);
socket.value.onopen = () => {
isConnected.value = true;
error.value = null;
reconnectAttempts = 0;
startHeartbeat();
};
socket.value.onmessage = (event) => {
try {
lastMessage.value = JSON.parse(event.data);
} catch (e) {
lastMessage.value = event.data;
}
};
socket.value.onerror = (err) => {
error.value = err;
};
socket.value.onclose = () => {
isConnected.value = false;
stopHeartbeat();
attemptReconnect();
};
};
const attemptReconnect = () => {
if (reconnectAttempts < maxReconnectAttempts) {
reconnectAttempts++;
setTimeout(() => {
connect();
}, reconnectInterval * Math.min(reconnectAttempts, 5));
} else {
error.value = new Error('Maksimal percobaan rekoneksi tercapai.');
}
};
const startHeartbeat = () => {
heartbeatTimer = setInterval(() => {
if (socket.value && socket.value.readyState === WebSocket.OPEN) {
socket.value.send(JSON.stringify({ action: 'ping' }));
}
}, 30000);
};
const stopHeartbeat = () => {
if (heartbeatTimer) clearInterval(heartbeatTimer);
};
const sendMessage = (data) => {
if (socket.value && socket.value.readyState === WebSocket.OPEN) {
socket.value.send(JSON.stringify(data));
} else {
console.error('WebSocket tidak terhubung. Pesan gagal dikirim.');
}
};
onMounted(() => {
connect();
});
onUnmounted(() => {
stopHeartbeat();
if (socket.value) {
socket.value.close();
}
});
return {
isConnected,
lastMessage,
error,
sendMessage
};
}
D. Integrasi Komponen Vue 3
Implementasikan composable tersebut ke dalam komponen e-commerce (InventoryDashboard.vue):
<script setup>
import { ref, watch } from 'vue';
import { useWebSocket } from '@/composables/useWebSocket';
const inventory = ref([
{ id: 101, name: 'Mechanical Keyboard Pro', stock: 45 },
{ id: 102, name: 'UltraWide Monitor 34 inch', stock: 12 },
{ id: 103, name: 'Ergonomic Office Chair', stock: 8 }
]);
const { isConnected, lastMessage, error, sendMessage } = useWebSocket('wss://api.ecommerce-realtime.internal/ws');
watch(lastMessage, (newVal) => {
if (newVal && newVal.productId && newVal.newStock !== undefined) {
const item = inventory.value.find(i => i.id === newVal.productId);
if (item) {
item.stock = newVal.newStock;
}
}
});
</script>
<template>
<div class="dashboard-container">
<header class="status-bar">
<h2>Real-Time Inventory Dashboard</h2>
<span :class="['badge', isConnected ? 'online' : 'offline']">
{{ isConnected ? 'Terhubung (Live)' : 'Terputus (Mencoba Rekoneksi...)' }}
</span>
</header>
<div v-if="error" class="alert-error">
{{ error.message || 'Terjadi kesalahan pada koneksi WebSocket.' }}
</div>
<div class="grid-products">
<div v-for="product in inventory" :key="product.id" class="product-card">
<h3>{{ product.name }}</h3>
<p class="stock-count">Stok Tersedia: <strong :class="{ 'low-stock': product.stock < 10 }">{{ product.stock }}</strong></p>
</div>
</div>
</div>
</template>
<style scoped>
.dashboard-container { max-width: 900px; margin: 0 auto; font-family: sans-serif; }
.status-bar { display: flex; justify-content: space-between; align-items: center; margin-bottom: 2rem; }
.badge { padding: 0.5rem 1rem; border-radius: 4px; font-weight: bold; }
.badge.online { background-color: #d4edda; color: #155724; }
.badge.offline { background-color: #f8d7da; color: #721c24; }
.grid-products { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 1.5rem; }
.product-card { border: 1px solid #ddd; padding: 1.5rem; border-radius: 8px; background: #fff; }
.low-stock { color: #dc3545; font-size: 1.2rem; }
</style>
6. Bedah Kode & Penjelasan Detail Baris Kunci
map $http_upgrade $connection_upgradedi Nginx: Bagian ini krusial agar Nginx meneruskan headerUpgradedanConnectionke backend. Tanpa pemetaan ini, koneksi standar HTTP tidak akan pernah berubah menjadi protokol WebSocket.proxy_read_timeout 3600s: Standar Nginx secara default memutuskan koneksi idle dalam 60 detik. Untuk aplikasi real-time, parameter ini wajib ditingkatkan agar koneksi persisten tidak terputus secara prematur.useWebSocket.js- Exponential Backoff (reconnectInterval * Math.min(reconnectAttempts, 5)): Mencegah ribuan klien melakukan thundering herd problem (menyerang server secara bersamaan) saat terjadi gangguan jaringan massal.startHeartbeat: Mengirim paket ping setiap 30 detik untuk mendeteksi dead connection (misalnya pengguna masuk ke mode sleep atau terputus tanpa event close yang bersih).
7. Skenario Produksi & Studi Kasus Dunia Nyata
Dalam skenario Flash Sale dengan 100.000 pengguna aktif bersamaan, server WebSocket tunggal akan mengalami kehabisan deskriptor file (file descriptor exhaustion). Untuk mengatasinya:
- Horizontal Scaling dengan Redis Pub/Sub: Deploy beberapa node instance server WebSocket di belakang Load Balancer (seperti AWS ALB atau Nginx). Ketika ada perubahan stok dari worker database, pesan dipublikasikan ke Redis Channel, yang kemudian disebar ke seluruh instance WebSocket.
- Linux Kernel Tuning (
/etc/sysctl.conf):fs.file-max = 2097152 net.core.somaxconn = 65535 net.ipv4.tcp_rmem = 4096 87380 16777216 net.ipv4.tcp_wmem = 4096 65536 16777216
8. Common Pitfalls, Anti-Patterns & Solusi
- Anti-Pattern 1: Menyimpan instance WebSocket di Vue
reactive()atauref()global secara langsung. Hal ini dapat memicu reaktivitas Vue yang tidak perlu dan merusak referensi pointer memori objek asli WebSocket.- Solusi: Bungkus instance WebSocket di dalam fungsi composable atau gunakan fungsi lokal tertutup (closure).
- Anti-Pattern 2: Tidak menangani memori bocor (memory leak) pada event listener. Lupa memanggil pembersihan interval timer (
clearInterval) saat komponen di-unmount akan menyebabkan konsumsi memori browser membengkak secara perlahan.- Solusi: Selalu gunakan hook
onUnmounted()untuk membersihkan timer dan menutup koneksi socket secara eksplisit.
- Solusi: Selalu gunakan hook
9. Benchmark, Metrik Kinerja & Tuning Performa
Untuk menguji ketahanan server WebSocket, gunakan tool benchmark khusus seperti autocannon atau artillery:
npm install -g artillery
artillery quick --count 10 -n 20 wss://api.ecommerce-realtime.internal/ws
Metrik utama yang harus dipantau:
- Concurrent Connections: Jumlah koneksi TCP aktif yang mampu dipertahankan server.
- CPU & RAM per 1000 connections: Memastikan footprint memori tetap efisien (idealnya < 20MB RAM per 1000 koneksi persisten).
- Message Latency: Waktu tempuh dari server mempublikasikan data hingga diterima oleh klien Vue.js.
10. Checklist Penerapan Produksi & Kesimpulan
- Konfigurasi SSL/TLS aktif (WSS wajib menggunakan sertifikat valid, bukan self-signed di produksi).
- Parameter Nginx
proxy_read_timeoutdankeepalivetelah disesuaikan. - Mekanisme Heartbeat / Ping-Pong aktif untuk mendeteksi zombie connections.
- Exponential backoff reconnection diimplementasikan di frontend untuk mencegah lonjakan trafik saat pemulihan jaringan.
- Redis Pub/Sub dikonfigurasi jika menggunakan kluster backend multi-node.
Dengan menggabungkan kekuatan Vue 3 Composition API secara modular dan arsitektur backend WebSocket yang di-tuning dengan benar, aplikasi e-commerce Anda kini siap menangani jutaan transaksi real-time dengan latensi minimal dan stabilitas tingkat enterprise.