chunkwise.

Stream bytes without counting them by hand

chunkwise splits readable streams into fixed-size chunks, reads and writes HTTP Range headers, and paces throughput so a single client cannot saturate a connection. No dependencies, about 4 kB minified.

$ npm i chunkwise
v1.4.2 MIT Node 18+ · ESM & CJS 0 dependencies

A ten-line example

Serving a partial video response is the case the library was written for. Parse what the client asked for, clamp it to the size of the file, and stream back only that window.

import { parseRange, formatContentRange, chunkBy } from 'chunkwise';

const range = parseRange(req.headers.range, file.size);

if (!range) {
  res.writeHead(416, { 'Content-Range': `bytes */${file.size}` });
  return res.end();
}

res.writeHead(206, {
  'Content-Range': formatContentRange(range, file.size),
  'Content-Length': range.end - range.start + 1,
});

for await (const chunk of chunkBy(file.slice(range), 64 * 1024)) {
  res.write(chunk);
}
res.end();

Read the API reference

What it covers

chunkBy()

Turns any readable stream into an async iterator of equal-sized buffers. The final chunk is short rather than padded, which is what almost every consumer expects and few implementations get right.

parseRange()

Handles the parts of RFC 7233 that come up in practice: open-ended ranges, suffix ranges, and requests that overshoot the end of the resource. Returns null when the header cannot be satisfied.

throttle()

Paces a stream to a byte budget per second using a token bucket. Useful when one download would otherwise starve every other request on the same process.

readAll()

Collects a stream into a single buffer with a hard ceiling, so a request body that never ends cannot exhaust memory. Rejects once the limit is crossed instead of truncating silently.

Why not just use the platform

Most of this can be written by hand, and for one endpoint it probably should be. The functions here exist because the same forty lines kept reappearing across services, each copy slightly different, and the differences were always in the edge cases: a suffix range longer than the file, a chunk boundary landing exactly on the end of a stream, a throttle that drifts because it measures wall-clock time instead of accumulated bytes.

Everything is written against WHATWG streams, so the same code runs in Node and in the browser. Node's own Readable is accepted too and converted internally.