Your Node.js script works perfectly with test data. Then you feed it a real 10GB log file. Suddenly: crash. No warnings, just ENOMEM. Here's why even seasoned developers make this mistake—and the bulletproof solution.


The Root of All Evil: fs.readFile

fs.readFile is the equivalent of dumping a dump truck’s contents into your living room. It loads every single byte into RAM before you can touch it. Observe:

// Processing a 3GB database dump? Enjoy 3GB RAM usage
fs.readFile('./mega-database.sql', 'utf8', (err, data) => {
  parseSQL(data); // Hope you have 3GB to spare
});
  • CLI tools crash processing large CSVs
  • Data pipelines implode on video files
  • Background services die silently at 3AM

This isn’t “bad code”—it’s how fs.readFile operates. And it’s why your production system fails catastrophically.


Streams: The Memory Ninja Technique

Streams process data like a conveyor belt—small chunks enter, get processed, then leave memory forever. No RAM explosions:

// Process 100GB file with ~50MB memory
const stream = fs.createReadStream('./giant-dataset.csv');

stream.on('data', (chunk) => {
  analyzeChunk(chunk); // Work with 64KB-1MB pieces
});

stream.on('end', () => {
  console.log('Processed entire file without going nuclear');
});

Real-World Massacre: File Processing

The Suicide Approach (Common Mistake)

// Data import script that crashes on big files
function importUsers() {
  fs.readFile('./users.json', (err, data) => {
    JSON.parse(data).forEach(insertIntoDatabase); // 💀
  });
}

The Stream Survival Guide (Correct Way)

// Processes 50GB JSON file without memory issues
const ndjsonStream = fs.createReadStream('./users.ndjson');
const jsonParser = new TransformStream({ /* parse line-by-line */ });

ndjsonStream.pipe(jsonParser)
  .on('data', (user) => insertIntoDatabase(user));

Streams handle terabyte-scale files like they’re nothing. Your app stays responsive—no OOM crashes.


Pro Tip: Streams + Pipelines = Unstoppable

Combine streams with Node.js’ pipeline for error-proof processing:

const { pipeline } = require('stream');
const zlib = require('zlib');

// Compress 20GB log file with constant memory
pipeline(
  fs.createReadStream('./server.log'),
  zlib.createGzip(), // Compress chunk-by-chunk
  fs.createWriteStream('./server.log.gz'),
  (err) => {
    if (err) console.error('Pipeline failed:', err);
    else console.log('Compressed 20GB file like a boss');
  }
);

When to Use Which Weapon

fs.readFile (Handle With Care):

  • Configuration files (<5MB)
  • Small static assets (icons, tiny JSON)
  • Only when you absolutely need all data in memory

fs.createReadStream (Default Choice):

  • Log processing
  • Media encoding/transcoding
  • Database imports/exports
  • Any file bigger than your phone’s RAM

The Harsh Truth

fs.readFile is Node.js’ version of a loaded gun—safe in controlled environments, deadly in production. Streams aren’t “advanced” techniques; they’re essential survival skills for any serious Node.js developer.

Next time you write fs.readFile, ask: “Will this file ever grow?” If the answer is yes (and it always is), you’ve just found your memory leak. Switch to streams—before your pager goes off at midnight.