Back to Blog
IoT Dashboard

Node.js WebSocket Server: The Real-Time Backend for IoT Dashboards

Building the WebSocket layer between your MQTT broker and React dashboard requires more than a simple ws server. This guide covers authenticated rooms, MQTT bridging, Redis pub/sub for horizontal scaling, and the connection patterns that handle 10,000 concurrent dashboard users.

October 10, 2024
12 min read
Node.jsWebSocketIoTMQTT

Node.js WebSocket Server: The Real-Time Backend for IoT Dashboards

Your IoT data pipeline ends at an MQTT broker. Your dashboard starts at a WebSocket connection. The Node.js backend in between is where most teams make mistakes that cause memory leaks, reconnection storms, and inability to scale beyond a single server.

This guide builds a production WebSocket server that bridges MQTT telemetry to browser clients, handles authentication, implements device rooms, and scales horizontally with Redis pub/sub.

Why Not Just Expose MQTT Directly?

Technically possible, but problematic:

  • 1. Security: Exposing MQTT broker publicly gives clients direct access to your device topics
  • 2. Filtering: Dashboard users should see only their own devices — MQTT policies alone can't enforce this
  • 3. Transformation: Raw MQTT payloads often need enrichment (device metadata, unit conversion)
  • 4. Aggregation: Mobile dashboards need less data than the backend processes
  • The WebSocket backend is your control layer.

    Tech Stack

    {
      "ws": "^8.17.0",
      "mqtt": "^5.8.0",
      "ioredis": "^5.3.0",
      "jsonwebtoken": "^9.0.0",
      "express": "^4.19.0"
    }
    

    Step 1: Basic WebSocket Server with JWT Auth

    import { WebSocketServer, WebSocket } from 'ws'
    import { IncomingMessage } from 'http'
    import jwt from 'jsonwebtoken'
    import { parse } from 'url'

    interface AuthenticatedSocket extends WebSocket { userId: string deviceIds: string[] isAlive: boolean }

    const wss = new WebSocketServer({ port: 8080 })

    wss.on('connection', (ws: AuthenticatedSocket, req: IncomingMessage) => { // Extract JWT from query param or Authorization header const { query } = parse(req.url ?? '', true) const token = query.token as string

    try { const payload = jwt.verify(token, process.env.JWT_SECRET!) as { userId: string deviceIds: string[] } ws.userId = payload.userId ws.deviceIds = payload.deviceIds ws.isAlive = true } catch { ws.close(4001, 'Unauthorized') return }

    // Subscribe this client to their device rooms for (const deviceId of ws.deviceIds) { subscribeClientToDevice(ws, deviceId) }

    ws.on('message', (data) => handleClientMessage(ws, data)) ws.on('pong', () => { ws.isAlive = true }) ws.on('close', () => unsubscribeClient(ws))

    ws.send(JSON.stringify({ type: 'connected', userId: ws.userId })) })

    // Heartbeat — detect dead connections every 30s const heartbeat = setInterval(() => { wss.clients.forEach((ws) => { const client = ws as AuthenticatedSocket if (!client.isAlive) { unsubscribeClient(client) return client.terminate() } client.isAlive = false client.ping() }) }, 30_000)

    wss.on('close', () => clearInterval(heartbeat))

    Step 2: Device Room Registry

    // Device room: Map>
    const deviceRooms = new Map>()

    function subscribeClientToDevice(ws: AuthenticatedSocket, deviceId: string) { if (!deviceRooms.has(deviceId)) { deviceRooms.set(deviceId, new Set()) } deviceRooms.get(deviceId)!.add(ws) }

    function unsubscribeClient(ws: AuthenticatedSocket) { for (const room of deviceRooms.values()) { room.delete(ws) } }

    function broadcastToDevice(deviceId: string, message: object) { const room = deviceRooms.get(deviceId) if (!room?.size) return

    const payload = JSON.stringify(message) for (const client of room) { if (client.readyState === WebSocket.OPEN) { client.send(payload) } } }

    Step 3: MQTT Bridge

    import mqtt from 'mqtt'

    const mqttClient = mqtt.connect('mqtts://your-broker:8883', { cert: readFileSync('./certs/client.crt'), key: readFileSync('./certs/client.key'), ca: readFileSync('./certs/ca.crt'), rejectUnauthorized: true, })

    mqttClient.on('connect', () => { // Subscribe to all device telemetry mqttClient.subscribe('devices/+/telemetry', { qos: 1 }) mqttClient.subscribe('devices/+/status', { qos: 1 }) })

    mqttClient.on('message', async (topic, payload) => { // Extract device ID from topic: devices/{deviceId}/telemetry const parts = topic.split('/') const deviceId = parts[1] const type = parts[2]

    if (!deviceRooms.has(deviceId)) return // No subscribers, skip

    const data = JSON.parse(payload.toString())

    if (type === 'telemetry') { // Enrich with device metadata from cache const meta = await deviceMetaCache.get(deviceId) broadcastToDevice(deviceId, { type: 'telemetry', deviceId, payload: data, meta, serverTs: Date.now(), }) }

    if (type === 'status') { broadcastToDevice(deviceId, { type: 'device_status', deviceId, isOnline: data.online, lastSeen: data.ts, }) } })

    Step 4: Redis Pub/Sub for Horizontal Scaling

    A single Node.js process handles ~50,000 concurrent WebSocket connections. For larger deployments or multi-region, use Redis to fan out across instances:

    import Redis from 'ioredis'

    const publisher = new Redis(process.env.REDIS_URL!) const subscriber = new Redis(process.env.REDIS_URL!)

    // Subscribe to the Redis channel for device events subscriber.subscribe('iot:telemetry')

    subscriber.on('message', (_channel: string, message: string) => { const { deviceId, payload } = JSON.parse(message) broadcastToDevice(deviceId, payload) // Send to clients on THIS instance })

    // When MQTT receives data, publish to Redis (all instances receive it) mqttClient.on('message', (topic, rawPayload) => { const deviceId = topic.split('/')[1] const payload = JSON.parse(rawPayload.toString())

    publisher.publish('iot:telemetry', JSON.stringify({ deviceId, payload })) })

    With this pattern, a load balancer (sticky sessions not required) distributes WebSocket clients across 5 Node.js instances. All instances see all device events via Redis and forward to their local clients.

    Step 5: Client-Initiated Commands

    function handleClientMessage(ws: AuthenticatedSocket, data: Buffer) {
      let msg: { type: string; deviceId: string; command: object }
      try {
        msg = JSON.parse(data.toString())
      } catch {
        return ws.send(JSON.stringify({ error: 'Invalid JSON' }))
      }

    // Authorization: client can only command their own devices if (!ws.deviceIds.includes(msg.deviceId)) { return ws.send(JSON.stringify({ error: 'Forbidden' })) }

    if (msg.type === 'command') { const topic = devices/${msg.deviceId}/commands const payload = JSON.stringify(msg.command) mqttClient.publish(topic, payload, { qos: 1 }) ws.send(JSON.stringify({ type: 'command_sent', deviceId: msg.deviceId })) } }

    Connection State Management

    Track server-side metrics for observability:

    // Metrics endpoint
    app.get('/metrics', (_req, res) => {
      res.json({
        connections:    wss.clients.size,
        deviceRooms:    deviceRooms.size,
        mqttConnected:  mqttClient.connected,
        uptime:         process.uptime(),
        memoryMB:       Math.round(process.memoryUsage().heapUsed / 1024 / 1024),
      })
    })
    

    Alert on: connection count growth > 20% per minute (reconnection storm), memory > 80% of limit (memory leak), MQTT disconnects.

    Need this backend built for your IoT dashboard? [Contact Code Caracal](/contact) — we've shipped WebSocket servers handling 50,000+ concurrent dashboard connections.

    Written by CodeCaracal Engineering

    We write from production experience — every technique in our articles has been deployed to real clients. No academic theory.

    More Articles

    Business · 12 min read

    IoT Device Compliance: FCC, CE, and Product Certification Guide for Hardware Startups

    Business · 11 min read

    What to Look for When Hiring an IoT Development Partner: 8 Critical Criteria

    Business · 11 min read

    IoT MVP to Production: Realistic Timeline and Budget for Hardware Startups

    Business · 11 min read

    IoT Development Agency vs Building In-House: A Decision Framework for Founders

    IoT Dashboard · 13 min read

    Next.js IoT Analytics Dashboard: From Sensor Data to Production App

    Business · 11 min read

    How Much Does It Cost to Build an IoT Product in 2024? A Realistic Breakdown

    IoT Dashboard · 11 min read

    IoT Dashboard UX: Design Principles for Industrial Monitoring Interfaces

    Cloud & DevOps · 12 min read

    Containerizing IoT Backend Services with Docker: From Dev to Production

    IoT Dashboard · 14 min read

    Grafana + InfluxDB IoT Monitoring: Complete Production Setup Guide

    IoT Dashboard · 12 min read

    Building Real-Time IoT Dashboards with React and Recharts

    Cloud & DevOps · 13 min read

    CI/CD for Embedded Firmware: Automated Build, Test, and OTA Release Pipeline

    Mobile Development · 12 min read

    Flutter Offline-First IoT Apps: Hive + Sync Architecture That Works in the Field

    Cloud & DevOps · 14 min read

    Terraform for IoT Infrastructure: Provisioning AWS IoT Core, Lambda, and InfluxDB as Code

    Mobile Development · 10 min read

    Flutter IoT Alerts: Firebase Push Notifications for Device Events

    Cloud & DevOps · 12 min read

    Deploying IoT Backends on AWS: ECS Fargate vs Lambda vs EC2 Decision Guide

    Mobile Development · 11 min read

    Flutter + MQTT: Building Production IoT Mobile Apps That Scale

    Mobile Development · 13 min read

    Flutter BLE: Building a Bluetooth IoT Controller App from Scratch

    Cloud & DevOps · 13 min read

    AWS IoT Core vs Azure IoT Hub vs Google Cloud IoT: 2024 Honest Comparison

    IoT Engineering · 13 min read

    Kafka vs RabbitMQ for IoT: Choosing the Right Message Queue for High-Volume Telemetry

    IoT Engineering · 14 min read

    IoT System Testing: Unit, Integration, Hardware-in-the-Loop, and End-to-End

    IoT Engineering · 14 min read

    Predictive Maintenance with IoT Sensor Data: From Threshold to Machine Learning

    Embedded Systems · 14 min read

    IoT Bootloader Design: Secure Boot, A/B Partitions, and Reliable OTA Recovery

    IoT Engineering · 14 min read

    Multi-Tenant IoT Platform Architecture: Isolation, Scaling, and Data Partitioning

    Embedded Systems · 14 min read

    Memory Management in Embedded Firmware: Avoiding Heap Fragmentation and Stack Overflows

    IoT Engineering · 13 min read

    IoT Cost Optimization: How We Cut AWS IoT Bills by 60% Without Sacrificing Reliability

    IoT Engineering · 12 min read

    Edge Computing in IoT: When to Process On-Device vs In the Cloud

    IoT Engineering · 13 min read

    Digital Twins for IoT: Building a Virtual Mirror of Your Physical Devices

    Embedded Systems · 14 min read

    ESP32 Deep Sleep Mastery: Cutting Power Consumption from 240mA to 10µA

    IoT Engineering · 10 min read

    MQTT QoS 0, 1, and 2 Explained: Choosing the Right Level for IoT

    IoT Engineering · 14 min read

    IoT Monitoring and Observability: Metrics, Logs, and Distributed Tracing

    Embedded Systems · 14 min read

    Debugging Embedded Firmware: JTAG, GDB, Logic Analyzers, and Serial Tracing

    IoT Engineering · 12 min read

    WebSocket vs MQTT vs Server-Sent Events: Real-Time IoT Protocol Deep Dive

    Embedded Systems · 13 min read

    STM32 HAL vs Low-Level Drivers: When the Abstraction Costs You Too Much

    IoT Engineering · 13 min read

    IoT Data Pipeline: From Raw Sensor Reading to Live Dashboard in Under 100ms

    IoT Engineering · 13 min read

    Zero-Touch IoT Device Provisioning: Scaling from 10 to 100,000 Devices

    Embedded Systems · 13 min read

    UART vs SPI vs I2C: Choosing the Right Protocol for Sensor Integration

    IoT Engineering · 12 min read

    Real-Time IoT Alerting: From Simple Thresholds to ML Anomaly Detection

    Embedded Systems · 12 min read

    ESP32 Partition Table: Designing Flash Layout for Production Firmware

    IoT Engineering · 12 min read

    IoT Architecture Patterns: Hub-and-Spoke, Mesh, and Edge-Cloud Hybrid

    Embedded Systems · 13 min read

    IoT Battery Life Optimization: Engineering Devices That Last Years on a Single Charge

    IoT Engineering · 13 min read

    Time-Series Databases for IoT: InfluxDB vs TimescaleDB vs AWS Timestream

    Security · 14 min read

    Zero-Trust Security for Embedded IoT: Why Your Devices Are Probably Vulnerable

    Embedded Systems · 14 min read

    FreeRTOS on ESP32: Task Scheduling, Queues, and Resource Management for IoT

    IoT Engineering · 12 min read

    Building a Production IoT Gateway with Raspberry Pi and Node.js

    Embedded Systems · 13 min read

    ESP32 vs STM32: Choosing the Right Microcontroller for Your IoT Project

    Mobile Development · 10 min read

    Flutter + WebSocket: Building Real-Time IoT Dashboards That Don't Stutter

    IoT Engineering · 13 min read

    IoT Fleet Management at Scale: AWS IoT Core Device Registry and Provisioning

    IoT Engineering · 11 min read

    MQTT vs HTTP for IoT: Which Protocol Wins in Production?

    IoT Engineering · 12 min read

    ESP32 → MQTT → AWS IoT Core: The Production-Grade Architecture Guide

    Let's Build Together

    Got an IoT challenge?
    We've shipped it.

    Whether you need a fleet to track, a factory to monitor, or a farm to automate — our team has done it before and we'd love to build it with you. Typical response time: under 24 hours.

    No upfront commitment99.9% uptime SLANDA on requestFixed-price options