How to Create Directory When Writing to File in Node.js

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

  1. Path walk – Node checks each segment (D:datatmp).
  2. Missing link – The first segment that fails a stat (tmp) triggers the OS error ENOENT (Error NO ENTity).
  3. 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 (adds ensureFile, 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:

  1. Builds the full date‑based folder path.
  2. Creates any missing directories.
  3. Appends your message (instead of nuking the file every run).
  4. Echoes the current file contents so you know it worked.

Five Fun Extensions

IdeaWhat to Explore
Dynamic pathsUse new Date() to auto‑stamp yearly / monthly / daily folders.
StreamsSwap appendFile for fs.createWriteStream() if you expect thousands of writes per second.
Log rotationCheck fs.stat()—when size > N KB, rename to app‑20250502‑120001.log.
ESM nicetiesNode 20 supports top‑level await; strip the wrapper functions altogether.
Unit testsFeed 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.

Related blog posts