Peer-to-Peer Architecture

How to Build a P2P File Sharer with WebRTC

April 30, 2026
8 Min Read

Cloud storage is expensive. If you are building an application that allows users to send 500MB video files to each other, routing that traffic through AWS S3 or Supabase Storage will bankrupt your project overnight.

The ultimate architectural cheat code for file sharing is WebRTC (Web Real-Time Communication). While most developers know WebRTC for video calling (like Zoom or Google Meet), it possesses a hidden superpower: the RTCDataChannel. This API allows you to stream raw binary files directly from one browser to another, bypassing the server entirely.

The Myth of the "Serverless" Connection

Before two browsers can establish a direct P2P connection, they need to know how to find each other on the chaotic public internet. They must exchange IP addresses, ports, and cryptographic session keys.

This initial handshake requires a Signaling Server. However, the signaling server only passes tiny JSON messages (Offers and Answers) for a few seconds. Once the connection is established, the server steps out of the way, and the heavy file transfer happens entirely P2P.

Step 1: Establishing the Peer Connection

First, we initialize the RTCPeerConnection object. We provide STUN servers (free public servers provided by Google) that help the browser figure out its own public IP address.

const configuration = {
    iceServers: [
        { urls: 'stun:stun.l.google.com:19302' },
        { urls: 'stun:stun1.l.google.com:19302' }
    ]
};

// Initialize the connection
const peerConnection = new RTCPeerConnection(configuration);

Step 2: Creating the Data Channel

Before we generate the connection "Offer", the sender must create the Data Channel. We configure it to send raw arraybuffer data, which is perfect for file transfers.

// The sender creates the channel
const dataChannel = peerConnection.createDataChannel('fileTransferChannel');
dataChannel.binaryType = 'arraybuffer';

dataChannel.onopen = () => {
    console.log("P2P Connection Established! Ready to send files.");
};

dataChannel.onclose = () => {
    console.log("P2P Connection Closed.");
};

Step 3: Streaming the File in Chunks

You cannot shove a 500MB file through a WebRTC channel all at once; it will overflow the browser's internal buffer and crash. You must slice the ArrayBuffer into smaller chunks (usually 16KB to 64KB) and send them sequentially.

/**
 * Streams a raw file buffer over a WebRTC Data Channel
 * @param {ArrayBuffer} fileBuffer - The raw file data
 * @param {RTCDataChannel} channel - The open WebRTC channel
 */
function sendFileInChunks(fileBuffer, channel) {
    const CHUNK_SIZE = 64 * 1024; // 64KB chunks
    let offset = 0;

    const sendChunk = () => {
        // While there is still data to send
        while (offset < fileBuffer.byteLength) {
            // Check if the WebRTC buffer is getting full
            if (channel.bufferedAmount > channel.bufferedAmountLowThreshold) {
                // Pause and wait for the buffer to clear
                channel.onbufferedamountlow = () => {
                    channel.onbufferedamountlow = null;
                    sendChunk();
                };
                return;
            }

            // Slice the buffer and send the chunk
            const chunk = fileBuffer.slice(offset, offset + CHUNK_SIZE);
            channel.send(chunk);
            offset += CHUNK_SIZE;
            
            // Optional: Update a progress bar UI here
        }
        
        console.log("File transfer complete!");
        // Send an EOF (End of File) signal so the receiver knows we're done
        channel.send(JSON.stringify({ type: 'EOF' }));
    };

    sendChunk();
}

Experience Serverless Sharing

We utilized this exact WebRTC RTCDataChannel architecture to build ZeroShare, a completely serverless, peer-to-peer file and text sharing application.

Because the files travel directly between devices, there are zero file size limits, zero cloud storage costs, and absolute privacy. Our servers never touch your files.

The Receiving End

On the receiver's side, you simply listen to the channel.onmessage event. As the 64KB chunks arrive, you push them into an array. Once you receive the 'EOF' signal, you combine the array into a Blob and trigger the download using the URL.createObjectURL() method we discussed in our previous post.

Conclusion

WebRTC is complex, but the payoff is immense. By offloading the heavy lifting of file transfers to the users' own network connections, you can build infinitely scalable file-sharing applications with effectively zero hosting costs. Combine WebRTC with the Web Crypto API, and you have the ultimate privacy-first architecture.