WebRTC gets called "peer-to-peer" as if that means no server is involved, which is a common misconception that trips up a lot of first attempts — you still need a signaling server to help two peers find and negotiate a connection with each other, WebRTC just skips routing the actual media/data through your server once that connection is established.
WebRTC (Web Real-Time Communication) is a browser API enabling direct peer-to-peer audio, video, and data communication without a media server relaying traffic in the middle. It handles the actual media/data transport peer-to-peer once connected, but establishing that connection (signaling — exchanging connection metadata between peers) is left to the application to implement using its own server infrastructure.
Why WebRTC Matters (and When to Skip It)
For real-time video/audio calling or low-latency peer-to-peer data (collaborative editing, gaming), WebRTC avoids the latency and bandwidth cost of routing all media through a central server — direct peer connections mean lower latency and lower server infrastructure cost for the actual media transport, which matters significantly at any real scale of video/audio traffic.
Skip WebRTC for use cases that don't need true real-time peer-to-peer transport — if you just need to display near-real-time updates (a live dashboard, chat messages), Server-Sent Events or WebSockets through your own server are simpler and don't require implementing signaling infrastructure or dealing with NAT traversal complexity.
Getting Started with WebRTC
Creating a peer connection and handling the offer/answer signaling exchange:
const peerConnection = new RTCPeerConnection({
iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
});
// caller creates an offer
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);
signalingChannel.send({ type: "offer", offer });
// callee receives the offer, creates an answer
peerConnection.setRemoteDescription(receivedOffer);
const answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(answer);
signalingChannel.send({ type: "answer", answer });
Adding local media to send to the peer:
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
stream.getTracks().forEach((track) => peerConnection.addTrack(track, stream));
Core WebRTC Concepts Every Developer Should Know
Signaling is entirely your responsibility, and WebRTC doesn't specify how to do it. Peers need to exchange session descriptions (offer/answer) and ICE candidates through some out-of-band channel before a direct connection can be established — typically a WebSocket connection to your own server, since WebRTC's specification deliberately leaves this part open rather than mandating a specific signaling protocol.
ICE (Interactive Connectivity Establishment) handles NAT traversal, the genuinely hard networking problem of establishing a direct connection between two peers that are each behind their own router/firewall. STUN servers help peers discover their public IP; TURN servers act as a relay fallback when a direct connection genuinely can't be established (some restrictive network configurations make this unavoidable).
const iceServers = [
{ urls: "stun:stun.l.google.com:19302" },
{ urls: "turn:turnserver.example.com", username: "user", credential: "pass" },
];
TURN servers are a necessary fallback, not an edge case to skip. A meaningful percentage of real-world connections (symmetric NATs, restrictive corporate firewalls) can't establish a direct peer connection and need a TURN relay — skipping TURN server setup means a real subset of your users simply won't be able to connect, often silently.
Data channels enable arbitrary peer-to-peer data transport, not just audio/video. RTCDataChannel supports low-latency data exchange for use cases like collaborative editing or peer-to-peer file transfer, using the same underlying peer connection infrastructure as media streams.
Common WebRTC Mistakes and How to Fix Them
Mistake 1: not deploying a TURN server, assuming STUN alone is sufficient for NAT traversal. Fix: deploy or use a managed TURN server, since a real portion of users behind restrictive NATs need it to connect at all.
Mistake 2: treating signaling as an afterthought, without robust handling for signaling failures, reconnection, or peers going offline mid-negotiation. Fix: build signaling with the same reliability considerations as any other real-time messaging system — retries, timeouts, and clear state management.
Mistake 3: reaching for raw WebRTC APIs for a use case that doesn't need true peer-to-peer transport. Fix: use WebSockets or Server-Sent Events for simpler real-time update use cases, reserving WebRTC for genuine audio/video/low-latency peer-to-peer needs.
When Should You Use WebRTC Instead of WebSockets?
Use WebRTC when you need real-time audio/video calling or genuinely low-latency peer-to-peer data transport where routing through your own server would add unacceptable latency or cost. Use WebSockets when you need real-time updates through your own server infrastructure — chat, notifications, live dashboards — where peer-to-peer transport isn't the actual requirement and a simpler client-server model suffices.
WebRTC in Production
Deploy a TURN server (or use a managed service) from the start, not as an afterthought, since skipping it silently excludes a meaningful fraction of real-world users behind restrictive networks. Also build signaling infrastructure with real reliability considerations — reconnection handling, timeout management — since it's just as critical to the user experience as the peer connection itself, despite being "just" the setup phase.
If you're building real-time video/audio calling or peer-to-peer collaborative features, WebRTC is the right tool, but budget real time for TURN infrastructure and robust signaling, not just the peer connection code itself.