fs.createReadStream()

Opens a file as a readable stream so large files are processed in chunks instead of loaded at once.

Signature
fs.createReadStream(path[, options])
  • Added in Node.js v0.1.31

Parameters

ParameterTypeDescription
$pathstring | Buffer | URLThe file to read. Ignored when options.fd is supplied.
$optionsoptionalstring | ObjectStream options. If a string is passed it is taken as the encoding.
$options.flagsoptionalstring= 'r'File system flags used when opening the file.
$options.encodingoptionalstring= nullAny encoding accepted by Buffer. When set, chunks are strings; when null, chunks are Buffers.
$options.fdoptionalinteger | FileHandle= nullAn already-open descriptor to read from. When given, path is ignored and no 'open' event is emitted.
$options.modeoptionalinteger= 0o666File mode, applied only if the file is created.
$options.autoCloseoptionalboolean= trueClose the descriptor automatically on 'error' or 'end'. When false you must close it yourself.
$options.emitCloseoptionalboolean= trueEmit a 'close' event after the stream is destroyed.
$options.startoptionalintegerFirst byte offset to read, inclusive, counting from 0.
$options.endoptionalinteger= InfinityLast byte offset to read, inclusive.
$options.highWaterMarkoptionalinteger= 64 * 1024Maximum bytes buffered before the stream stops reading ahead.
$options.fsoptionalObject | null= nullOverrides for the open, read and close implementations. An override for read is required when this is used.
$options.signaloptionalAbortSignal | null= nullAborting the corresponding controller destroys the stream.
$options.windowsHandleoptionalbigint= nullA raw Win32 HANDLE to read from in place of fd. Windows only; throws on other platforms.

Return value

fs.ReadStreamAn fs.ReadStream, a standard Readable that emits 'open', 'ready', 'data', 'end', 'close' and 'error' events.

Errors

Invalid start or end values throw ERR_OUT_OF_RANGE synchronously, but a missing or unreadable file does not throw — it surfaces asynchronously as an 'error' event carrying ENOENT or EACCES, which becomes an uncaught exception if no 'error' listener is attached.

Changelog

  • PHP v26.8.0Added the windowsHandle option.
  • PHP v15.5.0Added support for AbortSignal via the signal option.
  • PHP v15.4.0The fd option accepts FileHandle arguments.
  • PHP v14.0.0The emitClose default changed to true, so streams now emit 'close' after being destroyed unless you opt out.
  • PHP v11.0.0New restrictions on start and end; out-of-range values now throw ERR_OUT_OF_RANGE instead of behaving unpredictably.
  • PHP v7.6.0The path parameter can be a WHATWG URL object using the file: protocol.
  • PHP v2.3.0The options argument may be a string specifying the encoding.

fs.createReadStream() returns an fs.ReadStream — an ordinary readable stream backed by a file descriptor. Instead of materialising the whole file as one Buffer the way fs.readFile() does, it pulls highWaterMark bytes at a time (64 KiB by default), so peak memory stays flat whether the file is 5 KB or 5 GB.

Reach for it when the file is large or unbounded, when you want to start work before the last byte arrives, or when the data is heading somewhere that already speaks streams: an HTTP response, a gzip transform, readline for line-by-line parsing, or another file through stream.pipeline().

Things that bite

  • The file is opened after the call returns. A missing path does not throw from createReadStream(); it arrives as an 'error' event, and with no listener that crashes the process.
  • Without an encoding you get Buffer chunks. With one you get strings, and multibyte characters are decoded safely across chunk boundaries.
  • start and end are byte offsets and both are inclusive, so { start: 0, end: 9 } reads ten bytes, not nine.
  • Chunk boundaries are arbitrary. Never assume a chunk is a whole line or a complete JSON document.
  • Prefer stream.pipeline() over .pipe() so errors propagate and descriptors are always closed.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Serve a large video or export file over HTTP without buffering it in memory.
  • Parse a multi-gigabyte CSV or NDJSON log line by line with readline.
  • Read only the last few kilobytes of a log file using start and end offsets.
  • Pipe a file through zlib.createGzip() into a compressed copy on disk.

Frequently asked questions

When should I use fs.createReadStream instead of fs.readFile?

Use createReadStream when the file is large enough that holding it entirely in memory is a problem, or when you can start processing before the file ends — parsing logs, serving downloads, piping through compression. Use readFile for small configuration and template files, where a single Buffer is simpler and the whole content is needed at once anyway.

Why doesn't fs.createReadStream throw when the file doesn't exist?

Because the open happens asynchronously after the call returns, the ENOENT is delivered as an 'error' event on the returned stream rather than as a synchronous throw. Always attach an 'error' listener, or use stream.pipeline() which propagates the error to its callback or promise.

How do I read a file line by line in Node.js?

Wrap the stream with readline.createInterface({ input: fs.createReadStream(path), crlfDelay: Infinity }) and iterate it with for await. Splitting raw 'data' chunks on newlines is unreliable because chunk boundaries fall at arbitrary byte offsets and routinely cut a line in half.

More examples

Read a specific byte range

The output is '6789' with length 4, not 3 — end: 9 includes byte 9. This is the mechanism behind HTTP range requests and tail-style log readers: only the requested bytes are read from disk, so grabbing the last kilobyte of a 10 GB file costs one seek. Note the stream is consumed with for await, which is the async-iterator interface every readable stream supports.

Example · javascript
import fs from 'node:fs';
import fsp from 'node:fs/promises';

await fsp.writeFile('numbers.txt', '0123456789');

// start and end are both INCLUSIVE byte offsets.
const tail = fs.createReadStream('numbers.txt', { start: 6, end: 9, encoding: 'utf8' });

let out = '';
for await (const chunk of tail) out += chunk;

console.log('bytes 6..9:', out);
console.log('length:', out.length);

await fsp.unlink('numbers.txt');

Count lines without buffering the file

It prints 500. readline sits on top of the stream and does the reassembly that raw 'data' chunks cannot — a line split across two chunks is joined correctly, which is exactly the bug you get from splitting each chunk on '\n' yourself. Memory use is bounded by highWaterMark plus one line, so the same code works unchanged on a file far larger than RAM.

Example · javascript
import fs from 'node:fs';
import fsp from 'node:fs/promises';
import readline from 'node:readline';

await fsp.writeFile('big.txt', Array.from({ length: 500 }, (_, i) => `row ${i}`).join('\n') + '\n');

const rl = readline.createInterface({
  input: fs.createReadStream('big.txt', { encoding: 'utf8' }),
  crlfDelay: Infinity, // treat \r\n as one break
});

let lines = 0;
for await (const line of rl) lines++;

console.log('lines:', lines);

await fsp.unlink('big.txt');

Discussion

  • Be the first to comment on this lesson.