MuhDikhai: WebRTC signaling server with Node.js

By Yaduraj Singh.

WebRTC video chat with a Node.js signaling server, Socket.io offer/answer exchange, Redis matchmaking and configurable TURN fallback.

Problem

Build a video-chat product while understanding the complete WebRTC peer lifecycle: finding a partner, negotiating media, handling network changes and cleaning up after a disconnect. WebRTC transports media, but the application must supply its own signaling and matchmaking.

Engineering approach

Technical decisions

Offer, answer and ICE: the signaling sequence

SDP (Session Description Protocol) describes media capabilities. ICE (Interactive Connectivity Establishment) discovers usable network paths. The signaling server forwards these messages; it does not choose the final media path.

Read this sequence as Browser A → Node.js / Socket.io → Browser B. Media subsequently travels Browser A ↔ Browser B, potentially through TURN.

Browser A             Node.js / Socket.io             Browser B
   | -- SDP offer ----------> | -- SDP offer ------------> |
   | <--- SDP answer -------- | <--- SDP answer ----------- |
   | <--- ICE candidates ---> | <--- ICE candidates ------> |
   |                                                       |
   +========= WebRTC media (direct or via TURN) ============+

STUN and TURN: direct connections and relay fallback

STUN helps browsers discover externally reachable addresses. TURN relays media when a usable direct route is unavailable. The reviewed hook supplies Google STUN servers and adds TURN only when all three TURN settings are present.

The ICE policy is all, permitting direct and relay candidates. A configured relay is not proof that a particular call used it: inspect the selected candidate pair and test across separate networks. There is no claim here that a production coturn deployment was verified.

Matchmaking load, waiting users and queue limits

The current backend replaces the old in-memory-queue description with Redis partitions, heartbeats and a Lua transaction. Atomic matching reduces duplicate assignment when users join concurrently. A deferred consumer retries waiting entries without duplicating them.

Queue coordination is not the same as a hard capacity limit. I have not established a bounded queue or rejection policy from this review, so the former backpressure-on-overload claim has been removed.

Reconnect and cleanup: signaling versus media

When the last socket for a user disconnects, the server starts a five-second timer. A reconnect cancels that timer. On expiry, cleanup removes queue heartbeat and room state and notifies the partner.

The browser records disconnected and failed peer states. Socket reconnection and media recovery are separate concerns; a preserved room does not automatically repair an ICE path.

Source evidence and scope

This case study was checked against public source revision 8ae5018 on October 7, 2026. It describes source behavior, not a production load test. The earlier sub-two-second pairing metric is omitted because a reproducible benchmark was not available.

Technology stack

Node.js, TypeScript, WebRTC, Socket.io, Redis, PostgreSQL

Links

Project website

Source profile or repository

All projects · Contact Yaduraj Singh