Buffers, Streams, and Binary Data
Understand binary data, Buffers, readable and writable streams, piping, backpressure, and efficient data processing.
Understanding Buffers
A Buffer represents a fixed-size sequence of bytes. Buffers are important when working with files, network protocols, cryptography, images, compressed data, and other binary content.
Buffers are built on top of JavaScript typed-array capabilities but provide Node-specific APIs for binary data.
const buffer = Buffer.from('Hello Node.js', 'utf8');
console.log(buffer);
console.log(buffer.toString('utf8'));Buffer.from() converts text into bytes, while toString() converts those bytes back into text.- Buffers represent binary byte sequences.
- Buffers are commonly used by Node.js I/O APIs.
- Always know the encoding when converting between bytes and strings.
What does a Node.js Buffer represent?
Readable and Writable Streams
Streams process data incrementally instead of requiring the entire dataset to be loaded into memory. Readable streams produce data, writable streams consume data, and duplex or transform streams can do both.
Streams are particularly valuable for large files, HTTP requests, HTTP responses, compression, and network communication.
import { createReadStream } from 'node:fs';
const stream = createReadStream('large-file.txt', {
encoding: 'utf8'
});
stream.on('data', (chunk) => {
console.log('Received chunk:', chunk.length);
});
stream.on('end', () => {
console.log('Finished');
});- Streams reduce memory usage for large datasets.
- Readable streams produce chunks of data.
- Writable streams consume chunks of data.
What is a major advantage of streams?
Piping and Backpressure
Backpressure occurs when a writable destination cannot consume data as quickly as a readable source produces it. Node.js streams provide mechanisms for controlling this flow.
The pipe() method connects streams and handles important flow-control behavior automatically. Modern applications can also use pipeline() from node:stream/promises to compose streams while handling errors more safely.
pipeline() provides cleaner error handling and lifecycle management when connecting multiple streams.
import { createReadStream, createWriteStream } from 'node:fs';
import { pipeline } from 'node:stream/promises';
await pipeline(
createReadStream('input.txt'),
createWriteStream('output.txt')
);
console.log('Copy completed');pipeline() connects the streams and provides Promise-based completion and error propagation.- Backpressure prevents fast producers from overwhelming slow consumers.
- Use
pipeline()when composing multiple streams. - Stream errors must be handled explicitly.
What does backpressure protect against?