javascript 107 lines · 3 tabs

HTTP Range Requests for Video Streaming in Node.js With fs.createReadStream

Shared by codesnips Aug 2026
3 tabs
function parseRange(header, size) {
  if (!header || !header.startsWith('bytes=')) return null;

  const [rawStart, rawEnd] = header.replace('bytes=', '').split('-');
  let start;
  let end;

  if (rawStart === '') {
    // suffix range: last N bytes, e.g. bytes=-500
    const suffix = parseInt(rawEnd, 10);
    if (Number.isNaN(suffix)) return null;
    start = Math.max(size - suffix, 0);
    end = size - 1;
  } else {
    start = parseInt(rawStart, 10);
    end = rawEnd === '' ? size - 1 : parseInt(rawEnd, 10);
  }

  if (Number.isNaN(start) || Number.isNaN(end)) return null;
  if (start > end || start < 0 || end >= size) return null;

  return { start, end, contentLength: end - start + 1 };
}

module.exports = { parseRange };
3 files · javascript Explain with highlit

Serving video for <video> playback is not a plain file download: browsers issue partial Range requests so users can seek instantly and the player only buffers what it needs. Responding with the whole file on every request wastes bandwidth and breaks seeking. This snippet shows how to honor Range headers with fs.createReadStream and the correct 206 Partial Content semantics.

The range parser tab (parseRange helper) isolates the fiddly part: reading a bytes=start-end header and normalizing it against the file size. It handles the common forms — bytes=0-, bytes=500-999, and the suffix form bytes=-500 meaning the last N bytes — and returns null for anything malformed or unsatisfiable so the caller can decide how to respond. Because HTTP byte ranges are inclusive on both ends, contentLength is end - start + 1, a classic off-by-one trap the helper centralizes.

The streamVideo controller tab wires this into a request handler. It first fs.stats the file to learn the total size, which is needed both to validate the range and to build headers. When no Range header is present it falls back to a normal 200 full-body response with Accept-Ranges: bytes, signaling to the client that ranged requests are supported. When a range is present but unsatisfiable, it replies 416 with a Content-Range: bytes */size header as the spec requires.

For a valid range it sends 206 Partial Content with Content-Range, Content-Length, and the video MIME type, then pipes fs.createReadStream(path, { start, end }) into the response. Streaming rather than buffering keeps memory flat regardless of file size, and pipe propagates backpressure so a slow client cannot force the server to read faster than it can send. The error and close handlers matter in production: if the client aborts a seek mid-stream, the stream is destroyed to release the file descriptor, avoiding leaks under heavy scrubbing.

The server wiring tab shows the route mounted on a tiny Express app, resolving and constraining the requested filename to a media directory to prevent path traversal. Together these files form the minimal, correct core of a self-hosted video endpoint that behaves the way browsers expect.


Related snips

Share this code

Here's the card — post it anywhere.

HTTP Range Requests for Video Streaming in Node.js With fs.createReadStream — share card
Link copied