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.

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.
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:
metadata -> chunk 1 -> chunk 2 -> ... -> selesaiChunks of 16-64 KB are the sweet spot: big enough to be efficient, small enough for single messages the event loop can handle lightly.
Always send metadata first so the server knows what is coming.
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.
On the client side, the file is read and split with slice.
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).
The server collects chunks by transfer identity.
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 is computed from the chunks already sent.
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.
With numbered chunks, resume becomes easy: the client asks for the last chunk the server received.
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.
Chunk size affects throughput and latency:
For text and JSON files, compression shrinks the size drastically. For already-compressed media like JPEG images or video, extra compression only wastes CPU.
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.
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.
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.
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:
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.