Create a room, share the link, and talk face to face. Live video and audio stream directly between participants (WebRTC); a small Node server only handles room creation and signaling.
- Instant rooms — one click creates a room with a short shareable code (e.g.
AB2-XY7-Q9K) - The call opens as its own full page — creating or joining a room opens
call.htmlin a new tab/window with its own history and its own connection, instead of just swapping a panel on the lobby page - Invite by link — anyone who opens the link drops straight into the lobby with the code prefilled
- Live video & audio — peer-to-peer WebRTC, up to ~12 people per room
- Mic / camera toggles, with a real speaking indicator (actual mic-level detection, not a fake animation)
- Screen sharing — swap your camera for your screen mid-call (hidden automatically on devices that don't support it, e.g. most mobile browsers)
- In-room chat — text messages with timestamps, unread badge
- Reactions — floating emoji, broadcast to everyone in the room
- Participants panel — see who's in the room and who's the host
- Mobile-first responsive layout — safe-area padding for notches/home indicators, a dynamic-viewport-height fix for mobile browser toolbars, 16px inputs (no iOS auto-zoom), a compact layout for short/landscape screens, and a Screen Wake Lock so the display doesn't sleep mid-call
- No accounts, no database — everything lives in memory for the life of the room
videochat-app/
├── server.js Express + Socket.io signaling server
├── package.json
├── railway.json Railway deploy config
└── public/
├── index.html Lobby page — collect a name, create or join a room
├── call.html The call itself — its own full page (opened by the lobby)
├── style.css Shared design system + responsive layout
├── lobby.js Lobby logic — hands off to call.html
└── call.js WebRTC mesh logic, chat, reactions, call UI
Requires Node 18+.
npm install
npm startOpen http://localhost:3000 in two browser tabs (or two devices on the same network) to test a call with yourself.
Camera/microphone access requires either
localhostor HTTPS — both are fine for local dev, but a deployed version must be served over HTTPS, which Railway provides automatically.
Option A — from GitHub (recommended)
- Push this folder to a new GitHub repo.
- In Railway, click New Project → Deploy from GitHub repo and select it.
- Railway detects
package.json, runsnpm install, thennpm start(viarailway.json). - Once deployed, open the generated
*.up.railway.appURL — that's your app.
Option B — from the CLI
npm install -g @railway/cli
railway login
railway init
railway upNo environment variables are required to get started — Railway sets PORT
automatically and server.js reads it (process.env.PORT).
- Signaling only, not media. The server relays WebRTC offers/answers/ICE candidates and lightweight events (chat, reactions, join/leave) over Socket.io. Actual video/audio flows directly between browsers.
- Mesh topology. Every participant connects directly to every other participant. This keeps things simple and fast for small rooms (a handful of people); it isn't meant to scale to large webinars, since each participant's upload bandwidth is shared across every peer.
- Rooms live in memory. If the server restarts, active rooms are lost. That's fine for a single Railway instance; if you scale to multiple replicas, you'll need sticky sessions or to move room state into something shared like Redis, since Socket.io rooms are per-process.
WebRTC needs a path between two computers. STUN (already configured, using
Google's public servers) is enough for most home/office networks. Some
networks (strict corporate firewalls, some mobile carriers) require a TURN
relay to connect at all. To add one, open public/call.js and add your TURN
credentials to the ICE_SERVERS array near the top of the file:
const ICE_SERVERS = [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'turn:your-turn-host:3478', username: '...', credential: '...' },
];Managed TURN options include Twilio's Network Traversal Service, Cloudflare Calls, or a self-hosted coturn server.
- Persist chat history per room (Redis or a database)
- Recording (MediaRecorder on a combined canvas, or an SFU-based approach)
- Waiting room / host approval before joining
- Virtual backgrounds or blur (e.g. via MediaPipe)
- Move to an SFU (e.g. LiveKit, mediasoup) if you need rooms larger than ~12 people