Learn WebSocket - File Transfer over WebSocket
Episode 24 of 34

Learn WebSocket - File Transfer over WebSocket

This episode covers file transfer over WebSocket: chunked transfer with binary, progress tracking, pause and resume capability, and optimization of chunk size, compression, and bandwidth.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

Large files over plain HTTP mean uploading once then waiting. With WebSocket, the transfer can be more alive: progress visible per percent, pausable and resumable, even multiple files sent in parallel. But giant files can also explode memory if handled naively.

Episode 24 covers file transfer over WebSocket: splitting a file into chunks, sending them as binary, monitoring progress, and reassembling them on the server. You will build a transfer mechanism safe for files hundreds of megabytes large.

The Chunked Transfer Pattern

Why Chunk

A single WebSocket connection can handle giant frames, but do not: server memory will explode, and a single failure forces a full restart. Split the file into small chunks:

File transfer flow
metadata -> chunk 1 -> chunk 2 -> ... -> selesai

Chunks of 16-64 KB are the sweet spot: big enough to be efficient, small enough for single messages the event loop can handle lightly.

Metadata and Control

Always send metadata first so the server knows what is coming.

JSFile transfer metadata
const metadata = {
  type: "file:start",
  nama: "laporan.pdf",
  ukuran: 5242880,
  totalChunk: 320,
  chunkSize: 16384,
  id: "transfer_7f9a",
};

The metadata carries the name, size, and chunk count. The server validates the size against policy (for example a 100 MB maximum) before approving the transfer with the file:start-ok event.

Transfer Implementation

Sending Chunks from the Browser

On the client side, the file is read and split with slice.

JSSending a chunked file in the browser
const file = input.files[0];
const CHUNK = 16384;
 
for (let mulai = 0; mulai < file.size; mulai += CHUNK) {
  const chunk = file.slice(mulai, mulai + CHUNK);
  const buffer = await chunk.arrayBuffer();
  ws.send(buffer);
}

file.slice(mulai, mulai + CHUNK) takes part of the file without loading it all into memory, and chunk.arrayBuffer() turns it into data that can be sent via ws.send(buffer).

Reassembling on the Server

The server collects chunks by transfer identity.

JSThe server reassembles the file
const transfers = new Map();
 
ws.on("message", (data) => {
  if (data.toString().startsWith("{")) {
    const meta = JSON.parse(data.toString());
    transfers.set(meta.id, {
      nama: meta.nama,
      chunks: [],
      total: meta.totalChunk,
    });
    return;
  }
 
  const t = transfers.get(ws.transferId);
  t.chunks.push(Buffer.from(data));
  ws.emitProgres();
});

The server distinguishes JSON messages (control) from buffers (chunks) via data.toString().startsWith("{"). Chunks are collected in arrival order, then joined into a single Buffer when the count is complete.

Progress and Resume

Tracking Progress

Progress is computed from the chunks already sent.

JSComputing and sending progress
let terkirim = 0;
const total = 320;
 
for (let i = 0; i < total; i++) {
  await kirimChunk(i);
  terkirim += 1;
  ws.emit("file:progress", {
    persen: Math.round((terkirim / total) * 100),
  });
}

Math.round((terkirim / total) * 100) turns sent chunks into a percentage. The client displays a progress bar and updates it every chunk — a small detail that makes the transfer feel responsive.

Pause and Resume

With numbered chunks, resume becomes easy: the client asks for the last chunk the server received.

JSResuming from the last chunk
ws.on("file:resume", (id) => {
  const t = transfers.get(id);
  ws.send(JSON.stringify({
    type: "file:status",
    id,
    chunkBerikut: t.chunks.length,
  }));
});

chunkBerikut: t.chunks.length tells the client where to continue. A disconnected client just requests the status, then sends chunks starting from that number — no need to restart from the beginning.

Transfer Optimization

Chunk Size and Compression

Chunk size affects throughput and latency:

  • 16 KB: lowest latency, more header overhead.
  • 64 KB: a good balance for most networks.
  • 1 MB: maximum throughput, but one failure repeats a lot of data.

For text and JSON files, compression shrinks the size drastically. For already-compressed media like JPEG images or video, extra compression only wastes CPU.

Parallel Transfer

Several files can be sent concurrently over one connection with different transfer identities. The server processes parallel transfers with a count limit so they do not fight over bandwidth. Prioritize small transfers first so they do not wait behind a large file.

Use Cases

Images in Chat

A chat that sends images uses this pattern: a small thumbnail is sent first to display instantly, the original file follows behind. Users see a quick preview, then the full image appears when done.

Document Collaboration

Collaborative editors use WebSocket transfer for attachments: drag-and-drop images land in the document directly over WebSocket, appear as a placeholder, then finish as a full image.

Closing

Episode 24 gave you a file transfer mechanism that is safe and alive: chunks that protect memory, efficient binary, motivating progress, and resume that saves the day when a connection drops.

Key takeaways:

  • Split large files into chunks so memory does not explode.
  • Send metadata, then identify chunks with a transfer ID.
  • The browser reads files in parts with slice without loading them fully.
  • Progress is computed from sent chunks, not byte size.
  • Chunk numbers enable pause and resume from the break point.
  • Compression helps text files, is pointless for compressed media.

In the next episode we cover video/audio signaling with WebRTC: WebSocket's role as the signaling channel, SDP and ICE candidate exchange, and STUN and TURN configuration.

Learn WebSocket - File Transfer over WebSocket | Learn WebSocket