A full-stack, real-time voice + text chat application built with WebRTC, React, and Node.js.
┌─────────────────────────────────────────────────────────┐
│ Browser │
│ React Frontend (WebRTC + Web Audio API) │
│ - Peer connections (mesh topology) │
│ - Opus audio codec via WebRTC │
│ - Live waveform visualizer │
│ - Text chat panel │
└───────────────────┬─────────────────────────────────────┘
│ WebSocket (signaling only)
│ WebRTC P2P (audio data – bypasses server)
┌───────────────────▼─────────────────────────────────────┐
│ Node.js Signaling Server (Express + ws) │
│ - Room management (in-memory) │
│ - WebRTC offer/answer/ICE relay │
│ - Chat message relay + history (last 50 msgs) │
└─────────────────────────────────────────────────────────┘
Key design choices:
- P2P audio — audio streams flow directly between peers via WebRTC. The server only handles signaling (offer/answer/ICE candidates), never audio data.
- Opus codec — used automatically by WebRTC in all modern browsers; provides excellent quality at low bitrate.
- Ephemeral rooms — no database, no accounts. Rooms are destroyed when empty.
- End-to-end encryption — WebRTC uses DTLS-SRTP for all media streams by default.
- 🎙 Voice chat via WebRTC (P2P mesh)
- 💬 Text chat with message history
- 📊 Live waveform visualizer for local audio
- 🔊 Speaking indicators with real-time detection
- 🔒 E2E encrypted audio (DTLS-SRTP)
- 🚀 Ephemeral rooms — just share a code, no accounts needed
- 🔇 Mute toggle with visual feedback
cd backend
npm install
npm run dev # starts on :4000cd frontend
npm install
npm run dev # starts on :5173, proxies /ws to :4000Open http://localhost:5173, create a room, and open another tab to join.
- Docker + Docker Compose
- A server with ports 80/443 open
# Clone / copy this project to your server
git clone <your-repo> && cd voicechat
# Build and start
docker compose up -d --build
# Check logs
docker compose logs -fThe app will be available on port 80.
WebRTC and microphone access require HTTPS in production. The recommended approach is to put a TLS-terminating reverse proxy in front (e.g. Caddy or Certbot + nginx):
With Caddy (easiest):
# Install Caddy, then create Caddyfile:
yourdomain.com {
reverse_proxy localhost:80
}
caddy runWith Let's Encrypt + nginx directly:
certbot --nginx -d yourdomain.com| Variable | Default | Description |
|---|---|---|
PORT |
4000 |
Backend port |
NODE_ENV |
development |
Node environment |
VITE_WS_URL |
auto-detected | WebSocket URL for frontend |
This implementation uses an in-memory room store and full-mesh P2P topology. This works well up to ~6-8 participants per room. For larger scale:
- Signaling persistence: Replace in-memory store with Redis for multi-instance signaling
- SFU for large rooms: Use a Selective Forwarding Unit (e.g. mediasoup, Janus, LiveKit) instead of full mesh when rooms exceed 8 people
- TURN server: Add TURN servers (e.g. coturn) for users behind strict NAT/firewalls
# Add to docker-compose.yml for TURN:
coturn:
image: coturn/coturn
ports:
- "3478:3478/udp"
- "3478:3478/tcp"Then add to ICE_SERVERS in frontend/src/hooks/useVoiceChat.js:
{ urls: "turn:your-turn-server:3478", username: "user", credential: "pass" }voicechat/
├── backend/
│ ├── server.js # Express + WebSocket signaling server
│ ├── package.json
│ └── Dockerfile
├── frontend/
│ ├── src/
│ │ ├── App.jsx # Main app (lobby + room UI)
│ │ ├── App.module.css
│ │ ├── hooks/
│ │ │ └── useVoiceChat.js # WebRTC + signaling logic
│ │ └── components/
│ │ ├── PeerTile.jsx # Participant card
│ │ ├── ChatPanel.jsx # Text chat
│ │ └── Waveform.jsx # Audio visualizer
│ ├── nginx.conf
│ ├── vite.config.js
│ └── Dockerfile
└── docker-compose.yml