I wrote a tiny script to drop a text file into a fresh folder, hit Run in node.js, and boom ENOENT. That little red error message sent me down today’s rabbit‑hole. Here’s everything I learned, the code I ended up shipping, plus a few extra tricks you can play with right away.
The Face‑Plant: ENOENT
The very first run produced:
{ [Error: ENOENT, open 'D:\data\tmp\test.txt']
errno: 34,
code: 'ENOENT',
path: 'D:\\data\\tmp\\test.txt' }
Node looked for D:\data\tmp
, didn’t see it, and refused to create either the folder or the file. fs.writeFile()
happily overwrites files but it never lays down directories for you.
Why That Happens
- Path walk – Node checks each segment (
D:
➜data
➜tmp
). - Missing link – The first segment that fails a stat (
tmp
) triggers the OS errorENOENT
(Error NO ENTity). - Abort – The write never reaches the point where it could create
test.txt
.
In short: no folder, no file.
The Modern Fix (Node ≥ 10.12)
Since v10.12 we have fs.mkdir()
with a recursive: true
flag. One call spins up every absent folder in the chain.
const fs = require('fs/promises'); // Promise‑based API
const path = require('path');
async function writeFileEnsured(filePath, contents) {
const dir = path.dirname(filePath); // e.g. D:\data\tmp
await fs.mkdir(dir, { recursive: true }); // auto‑creates tree
await fs.writeFile(filePath, contents, 'utf8');
}
writeFileEnsured(
path.resolve(__dirname, 'tmp/test.txt'),
'Hey there!'
)
.then(() => console.log('✓ The file was saved!'))
.catch(console.error);
What changed?recursive: true
tells Node: “walk up the path and make every folder you don’t see.” No external packages; works on every LTS release after 2018.
Living in the Past (Node < 10.12)
Stuck on an ancient runtime? You can:
npm i mkdirp
(battle‑tested)npm i fs-extra
(addsensureFile
,ensureDir
, etc.)- Roll your own tiny helper:
const fs = require('fs');
const path = require('path');
function mkdirpSync(targetDir) {
const sep = path.sep;
const initDir = path.isAbsolute(targetDir) ? sep : '';
return targetDir.split(sep).reduce((parent, child) => {
const cur = path.resolve(parent, child);
if (!fs.existsSync(cur)) fs.mkdirSync(cur);
return cur;
}, initDir);
}
Call mkdirpSync('tmp')
before fs.writeFileSync()
and you’re back in business.
Mini Side Project: safeLog.js
I turned the pattern into a tiny CLI logger so I could dog‑food the idea.
#!/usr/bin/env node
// safeLog.js
import { promises as fs } from 'fs';
import path from 'path';
async function ensureDir(dir) {
await fs.mkdir(dir, { recursive: true });
}
async function appendSafe(file, text) {
await ensureDir(path.dirname(file));
await fs.appendFile(file, text + '\n', 'utf8');
}
async function readBack(file) {
return fs.readFile(file, 'utf8');
}
// ---- CLI glue ----
const [, , rawPath, ...msgParts] = process.argv;
if (!rawPath || msgParts.length === 0) {
console.error('Usage: safeLog <file> <message>');
process.exit(1);
}
const file = path.resolve(process.cwd(), rawPath);
const msg = msgParts.join(' ');
try {
await appendSafe(file, msg);
console.log('✓ Log entry saved.');
console.log(await readBack(file));
} catch (err) {
console.error('✗ Failed:', err);
}
Run:
node safeLog.js ./logs/2025/05/02/app.log "First run!"
The script:
- Builds the full date‑based folder path.
- Creates any missing directories.
- Appends your message (instead of nuking the file every run).
- Echoes the current file contents so you know it worked.
Five Fun Extensions
Idea | What to Explore |
---|---|
Dynamic paths | Use new Date() to auto‑stamp yearly / monthly / daily folders. |
Streams | Swap appendFile for fs.createWriteStream() if you expect thousands of writes per second. |
Log rotation | Check fs.stat() —when size > N KB, rename to app‑20250502‑120001.log . |
ESM niceties | Node 20 supports top‑level await ; strip the wrapper functions altogether. |
Unit tests | Feed ensureDir() a temp folder and assert it exists with Node’s built‑in assert/strict . |
Key Takeaways (a.k.a. The Sticky Bits)
ENOENT
means “something along that path is missing.”fs.writeFile()
can’t save you—create the folders first.- Since Node 10.12,
fs.mkdir({ recursive:true })
is the one‑liner solution. - Wrap that in a helper (
ensureDir
) once, and forget the pain forever.
Final Thought
I love moments like this: a tiny error ballooned into a neat side project and a reusable snippet I now drop into every quick script. Next time ENOENT
bites you, remember it’s just a missing folder, and you’ve already got the cure baked right into Node.