DEV Community

Cover image for Learn how to test native WebSockets, Socket.io, room messaging, typing indicators, and SSE streams in frontend apps without running local WS servers.
Nilesh Kumar
Nilesh Kumar

Posted on Originally published at playground.nileslabs.com

Learn how to test native WebSockets, Socket.io, room messaging, typing indicators, and SSE streams in frontend apps without running local WS servers.

Frontend engineers building real-time collaborative interfaces—such as live customer support widgets, multiplayer notification bars, or team chat channels—inevitably run into a painful testing hurdle:

"How do I test bi-directional message dispatching, typing indicators, reconnection loops, and multi-user room broadcasting before the backend WebSocket server is deployed?"

Traditionally, you either have to write a local Node.js ws or socket.io server script, manage local port tunnels with ngrok for mobile testing, or write brittle in-memory mock classes that don't test true RFC 6455 network frames.

In this guide, we explore the mechanics of real-time protocols, compare Native WebSocket vs Socket.io vs Server-Sent Events (SSE), and demonstrate how to build an interactive, multi-room chat client in React tested against Playground API's zero-config real-time gateway.


1. Comparing Real-Time Transport Protocols

Before writing frontend code, let's look at the three primary real-time communication patterns:

Protocol Comparison Matrix

Feature Native WebSocket (wss://) Socket.io (/socket.io) Server-Sent Events (SSE)
Directionality Full-Duplex Bi-directional Full-Duplex Bi-directional Unidirectional (Server → Client)
Browser Native? ✅ Yes (new WebSocket()) ❌ Requires client library ✅ Yes (new EventSource())
Automatic Reconnect ❌ Manual implementation ✅ Built-in ✅ Built-in by browser
Room / Namespace ❌ Manual routing ✅ Built-in ❌ Single stream URL
Best For Live chat, gaming, standard APIs Enterprise messaging, auto-fallbacks Live notifications, telemetry

2. Connecting to the Live Real-Time Gateway

Playground API provides a unified real-time gateway where Native WebSocket clients and Socket.io clients communicate seamlessly in the exact same room:

  • Native WebSocket: wss://playground.nileslabs.com/ws?room=general&username=Alice
  • Socket.io 4.x: https://playground.nileslabs.com with path /socket.io
  • Server-Sent Events (SSE): https://playground.nileslabs.com/api/v1/stream/notifications

The Standard Message Schema

All incoming and outgoing chat payloads follow this standardized format:

{
  "type": "message",
  "id": "msg-local-12345",
  "room": "support",
  "sender_id": 1,
  "sender_name": "Alice Developer",
  "recipient_id": "bot_assistant",
  "text": "Hello, I need help testing real-time events!",
  "created_at": "2026-09-19T10:00:00.000Z"
}
Enter fullscreen mode Exit fullscreen mode

3. Building a Real-Time React Chat Component

Let's build a clean, production-ready React chat component using the native browser WebSocket API with automatic reconnection, typing indicators, and room switching:

// src/components/LiveChatWidget.tsx
import React, { useState, useEffect, useRef } from 'react';

interface ChatMessage {
  id: string;
  room: string;
  sender_name: string;
  text: string;
  created_at: string;
}

export function LiveChatWidget() {
  const [messages, setMessages] = useState<ChatMessage[]>([]);
  const [inputText, setInputText] = useState('');
  const [room, setRoom] = useState('support');
  const [isConnected, setIsConnected] = useState(false);
  const [botTyping, setBotTyping] = useState(false);
  const wsRef = useRef<WebSocket | null>(null);

  useEffect(() => {
    // 1. Establish Native WebSocket connection with query parameters
    const wsUrl = `wss://playground.nileslabs.com/ws?room=${room}&username=FrontendEngineer`;
    const ws = new WebSocket(wsUrl);
    wsRef.current = ws;

    ws.onopen = () => {
      setIsConnected(true);
    };

    ws.onmessage = (event) => {
      try {
        const payload = JSON.parse(event.data);

        if (payload.type === 'message') {
          setMessages((prev) => [...prev, payload]);
        } else if (payload.type === 'typing') {
          setBotTyping(payload.isTyping);
        }
      } catch (err) {
        console.error('Invalid JSON payload:', event.data);
      }
    };

    ws.onclose = () => {
      setIsConnected(false);
    };

    return () => {
      ws.close();
    };
  }, [room]);

  function handleSendMessage(e: React.FormEvent) {
    e.preventDefault();
    if (!inputText.trim() || !wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) return;

    // 2. Dispatch message to the room
    const messagePayload = {
      type: 'message',
      room: room,
      sender_name: 'FrontendEngineer',
      text: inputText,
    };

    wsRef.current.send(JSON.stringify(messagePayload));
    setInputText('');
  }

  return (
    <div className="max-w-lg mx-auto p-4 bg-slate-900 border border-slate-800 rounded-2xl shadow-xl text-slate-100 font-sans">
      {/* Header & Status */}
      <div className="flex items-center justify-between pb-3 border-b border-slate-800">
        <div className="flex items-center gap-2">
          <span className={`w-2.5 h-2.5 rounded-full ${isConnected ? 'bg-emerald-500 animate-pulse' : 'bg-rose-500'}`} />
          <h3 className="font-bold text-sm text-white">Live Chat (#{room})</h3>
        </div>
        <select
          value={room}
          onChange={(e) => { setMessages([]); setRoom(e.target.value); }}
          className="bg-slate-950 text-xs px-2 py-1 rounded border border-slate-700 text-slate-300"
        >
          <option value="support">#support (Echo Bot Enabled)</option>
          <option value="general">#general</option>
          <option value="random">#random</option>
        </select>
      </div>

      {/* Messages Feed */}
      <div className="h-64 overflow-y-auto my-3 space-y-2 p-2 bg-slate-950/60 rounded-xl border border-slate-800/80 text-xs">
        {messages.length === 0 ? (
          <p className="text-slate-500 text-center py-20">No messages yet. Send a message to chat with the Echo Bot!</p>
        ) : (
          messages.map((msg, idx) => (
            <div key={msg.id || idx} className={`p-2 rounded-lg ${msg.sender_name === 'FrontendEngineer' ? 'bg-indigo-600/20 ml-6' : 'bg-slate-800/60 mr-6'}`}>
              <span className="font-bold text-slate-300 block">{msg.sender_name}:</span>
              <p className="text-slate-200 mt-0.5">{msg.text}</p>
            </div>
          ))
        )}
        {botTyping && (
          <div className="text-slate-400 italic text-[11px] animate-pulse">
            Support Bot is typing...
          </div>
        )}
      </div>

      {/* Input Form */}
      <form onSubmit={handleSendMessage} className="flex gap-2">
        <input
          type="text"
          value={inputText}
          onChange={(e) => setInputText(e.target.value)}
          placeholder="Type your message or mention @bot..."
          className="flex-1 bg-slate-950 border border-slate-700 px-3 py-2 rounded-lg text-xs text-white focus:outline-none focus:border-indigo-500"
        />
        <button
          type="submit"
          disabled={!isConnected}
          className="bg-indigo-600 hover:bg-indigo-500 disabled:bg-slate-800 text-white font-bold text-xs px-4 py-2 rounded-lg transition-colors"
        >
          Send
        </button>
      </form>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

4. Testing the Built-in Support Bot Simulator

When testing chat applications locally, it’s frustrating to test alone because you have to open two browser windows to simulate a conversation.

Playground API includes an Automated Echo Support Bot:

  1. When you join the #support room or mention @bot, the gateway automatically fires a { type: "typing", isTyping: true } event.
  2. After 250ms of natural typing delay, the bot replies with an echo acknowledgment.
  3. It automatically turns off the typing indicator event!

5. Summary & Key Takeaways

  1. Native WebSockets and Socket.io can be tested without local servers. By targeting wss://playground.nileslabs.com/ws, frontend developers can verify connection lifecycles, typing indicators, and room routing instantly.
  2. Server-Sent Events (SSE) provide lightweight unidirectional streams. Use GET /api/v1/stream/notifications for testing notification bells and real-time dashboard counters.
  3. Session state is preserved. Chat history created during your session persists across refreshes.

Explore the interactive live Chat Studio and copy starter code at:

👉 https://playground.nileslabs.com/docs/chat

Top comments (0)