In this lesson, you will learn how to create "middleware" streams that sanitize data while it travels from a source to a destination without ever loading the entire payload into RAM.
When handling large files—like user-uploaded CSVs or system logs—you cannot afford to read the whole file into a Buffer to perform regex replacements or character filtering. Instead, you can implement a custom Transform stream. A Transform stream acts as a bridge: it receives a chunk of data, performs a transformation, and pushes it forward to the next stage in your pipeline.
Building the Sanitizer
To create a custom transform, you extend the Transform class from the stream module and implement the _transform method. This method takes three arguments: the current chunk, the encoding, and a callback function. The callback is crucial; it signals that you are done processing the current chunk and ready for the next one, which automatically handles backpressure for you.
Here is how you might strip out sensitive numeric patterns (like credit card numbers) from a stream of text:
const { Transform } = require('stream');
class Sanitizer extends Transform {
_transform(chunk, encoding, callback) {
// Convert chunk to string, sanitize, then back to buffer
const sanitized = chunk.toString().replace(/\d{4}-\d{4}-\d{4}-\d{4}/g, '****-****-****-****');
this.push(sanitized);
callback(); // Signal completion
}
}
// Usage: read -> sanitize -> write
const sanitizer = new Sanitizer();
inputStream.pipe(sanitizer).pipe(outputStream);
Why this is efficient
Because the Sanitizer operates on one chunk at a time, your memory footprint remains constant regardless of whether you are processing a 1KB file or a 10GB file. The data "flows" through your logic in small, manageable pieces. By using this.push(), you emit the cleaned data to the next stream in the pipeline immediately, ensuring that no stale data sits in memory longer than necessary.
Key Takeaway: Custom Transform streams allow you to sanitize and clean data in-flight, maintaining a low memory profile by processing chunks sequentially rather than loading entire datasets into a buffer.