HeoLab
ToolsBlogAboutContact
HeoLab

Free developer tools with AI enhancement. Built for developers who ship.

Tools

  • JSON Formatter
  • JWT Decoder
  • Base64 Encoder
  • Timestamp Converter
  • Regex Tester
  • All Tools →

Resources

  • Blog
  • What is JSON?
  • JWT Deep Dive
  • Base64 Explained

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 HeoLab. All rights reserved.

Tools work in your browser. Zero data retention.

HomeBlogWebSockets: Building Real-Time Apps from Scratch
Table of Contents▾
  • How WebSockets Work
  • Native WebSocket API (Browser)
  • Node.js WebSocket Server (ws library)
  • Socket.io — WebSockets with Superpowers
  • Scaling with Redis Pub/Sub
  • WebSockets vs Server-Sent Events (SSE)
  • Reconnection Pattern
tutorials#websockets#real-time#node.js

WebSockets: Building Real-Time Apps from Scratch

Build real-time features with WebSockets — chat apps, live dashboards, collaborative editing. Covers the protocol, Socket.io, scaling with Redis, and when to use SSE instead.

Trong Ngo
February 25, 2026
4 min read

HTTP is request-response: the client asks, the server answers. WebSockets flip this model — both sides can send messages at any time. This is what powers real-time chat, live dashboards, collaborative editing, and multiplayer games.

How WebSockets Work

HTTP Upgrade handshake:

Client → Server:
  GET /chat HTTP/1.1
  Upgrade: websocket
  Connection: Upgrade
  Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==

Server → Client:
  HTTP/1.1 101 Switching Protocols
  Upgrade: websocket
  Connection: Upgrade
  Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

--- Connection is now a persistent TCP socket ---

Client → Server: "Hello!"      (any time)
Server → Client: "Hi there!"   (any time)
Server → Client: "New message from Alice"  (server-initiated!)

WebSocket URLs use ws:// (plain) or wss:// (TLS) instead of http://.

Native WebSocket API (Browser)

// Connect
const ws = new WebSocket("wss://api.example.com/ws")

// Connection opened
ws.addEventListener("open", () => {
  console.log("Connected!")
  ws.send(JSON.stringify({ type: "join", room: "general" }))
})

// Receive messages
ws.addEventListener("message", (event) => {
  const data = JSON.parse(event.data)
  console.log("Received:", data)
})

// Handle errors and disconnection
ws.addEventListener("error", (error) => console.error("WS Error:", error))
ws.addEventListener("close", (event) => {
  console.log(`Disconnected: ${event.code} ${event.reason}`)
  // Reconnect logic here
})

// Send a message
ws.send(JSON.stringify({ type: "message", text: "Hello, world!" }))

// WebSocket ready states
// ws.readyState === 0 → CONNECTING
// ws.readyState === 1 → OPEN
// ws.readyState === 2 → CLOSING
// ws.readyState === 3 → CLOSED

// Close gracefully
ws.close(1000, "User logged out")

Node.js WebSocket Server (ws library)

const { WebSocketServer } = require("ws")
const http = require("http")

const server = http.createServer()
const wss = new WebSocketServer({ server })

// Track connected clients by room
const rooms = new Map()  // roomId → Set<WebSocket>

wss.on("connection", (ws, request) => {
  const userId = getUserFromRequest(request)
  ws.userId = userId

  ws.on("message", (rawMessage) => {
    const message = JSON.parse(rawMessage.toString())

    switch (message.type) {
      case "join": {
        const room = rooms.get(message.room) ?? new Set()
        room.add(ws)
        rooms.set(message.room, room)
        ws.currentRoom = message.room
        break
      }
      case "message": {
        // Broadcast to everyone in the same room
        const room = rooms.get(ws.currentRoom)
        if (!room) return
        const payload = JSON.stringify({
          type: "message",
          from: ws.userId,
          text: message.text,
          timestamp: Date.now(),
        })
        for (const client of room) {
          if (client.readyState === WebSocket.OPEN) {
            client.send(payload)
          }
        }
        break
      }
    }
  })

  ws.on("close", () => {
    const room = rooms.get(ws.currentRoom)
    room?.delete(ws)
  })
})

server.listen(3001, () => console.log("WS server on :3001"))

Socket.io — WebSockets with Superpowers

// Server (Node.js)
const { Server } = require("socket.io")
const io = new Server(httpServer, {
  cors: { origin: "https://myapp.com" }
})

io.on("connection", (socket) => {
  console.log("User connected:", socket.id)

  // Join a room
  socket.on("join-room", (roomId) => {
    socket.join(roomId)
    socket.to(roomId).emit("user-joined", { userId: socket.id })
  })

  // Send to a room
  socket.on("send-message", ({ roomId, text }) => {
    io.to(roomId).emit("new-message", {
      from: socket.id,
      text,
      timestamp: Date.now(),
    })
  })

  socket.on("disconnect", (reason) => {
    console.log("Disconnected:", reason)
  })
})

// Client (browser)
import { io } from "socket.io-client"
const socket = io("wss://api.example.com")

socket.emit("join-room", "general")
socket.on("new-message", (msg) => displayMessage(msg))
socket.emit("send-message", { roomId: "general", text: "Hello!" })

Socket.io adds: automatic reconnection, rooms, namespaces, fallback to long-polling, and a broadcast API.

Scaling with Redis Pub/Sub

// When you have multiple server instances, clients on different servers
// can't see each other's messages without a message broker.

const { createClient } = require("redis")
const { createAdapter } = require("@socket.io/redis-adapter")

const pubClient = createClient({ url: REDIS_URL })
const subClient = pubClient.duplicate()

await Promise.all([pubClient.connect(), subClient.connect()])

io.adapter(createAdapter(pubClient, subClient))

// Now io.to("general").emit() works across ALL server instances
// Redis pub/sub routes the message to the right server

WebSockets vs Server-Sent Events (SSE)

FeatureWebSocketsSSE
DirectionBidirectionalServer → Client only
ProtocolCustom (ws://)HTTP
ReconnectManualAutomatic
Browser supportExcellentExcellent
Load balancersTricky (sticky sessions)Works with standard HTTP
Use caseChat, games, collaborationFeeds, notifications, dashboards
// SSE is simpler when you only need server → client
// app/api/feed/route.ts (Next.js)
export async function GET() {
  const stream = new ReadableStream({
    start(controller) {
      const send = (data: object) => {
        controller.enqueue(`data: ${JSON.stringify(data)}\n\n`)
      }

      // Subscribe to updates
      const unsub = events.on("post.created", (post) => send({ type: "new-post", post }))

      // Clean up when client disconnects
      return () => unsub()
    }
  })

  return new Response(stream, {
    headers: {
      "Content-Type": "text/event-stream",
      "Cache-Control": "no-cache",
    }
  })
}

Reconnection Pattern

// Exponential backoff reconnect
function connectWithRetry(url, onMessage) {
  let retries = 0
  let ws

  function connect() {
    ws = new WebSocket(url)

    ws.onopen = () => { retries = 0 }
    ws.onmessage = onMessage

    ws.onclose = () => {
      const delay = Math.min(1000 * 2 ** retries, 30000) // max 30s
      console.log(`Reconnecting in ${delay}ms...`)
      setTimeout(connect, delay)
      retries++
    }
  }

  connect()
  return () => ws.close()  // return cleanup function
}

Try These Tools

JSON Formatter & Validator

Format, validate, and beautify JSON data instantly. Detect errors with precise line numbers.

URL Parser & Builder

Parse any URL into its components and rebuild it. Edit parts individually.

Related Articles

PostgreSQL Performance Tuning: Indexes, Query Plans, and EXPLAIN ANALYZE

5 min read

Python Type Hints: The Complete Guide to typing, Pydantic, and mypy

4 min read

REST API Design Best Practices in 2025

4 min read

Back to Blog

Table of Contents

  • How WebSockets Work
  • Native WebSocket API (Browser)
  • Node.js WebSocket Server (ws library)
  • Socket.io — WebSockets with Superpowers
  • Scaling with Redis Pub/Sub
  • WebSockets vs Server-Sent Events (SSE)
  • Reconnection Pattern

Related Articles

PostgreSQL Performance Tuning: Indexes, Query Plans, and EXPLAIN ANALYZE

5 min read

Python Type Hints: The Complete Guide to typing, Pydantic, and mypy

4 min read

REST API Design Best Practices in 2025

4 min read