`node:fs/promises` — the API You Actually Want
Promise-Based fs
The promise-based fs API is the modern default. Combine with async/await for clean, non-blocking file IO.
What you'll learn
- Use readFile, writeFile, mkdir, rm
- Handle errors with try/catch
- Compose multiple operations
The promise-based fs API lives at node:fs/promises. It’s the
modern default — pairs perfectly with async/await.
Imports
import { readFile, writeFile, mkdir, rm, stat, readdir } from "node:fs/promises"; Or get the whole namespace:
import * as fs from "node:fs/promises";
await fs.readFile(...); Read
const text = await readFile("data.txt", "utf8"); Write
await writeFile("out.txt", "hello\n", "utf8"); Make Directory
await mkdir("logs/2026/may", { recursive: true }); recursive: true is like mkdir -p — creates intermediate dirs.
Delete
await rm("old-file.txt"); // file
await rm("temp-dir", { recursive: true }); // directory
await rm("maybe-gone", { force: true }); // ignore "not found" Errors
Promise APIs reject on errors. Catch with try/catch:
try {
const text = await readFile("missing.txt", "utf8");
} catch (err) {
if (err.code === "ENOENT") {
console.error("file not found");
} else {
throw err;
}
} Node attaches a code property (ENOENT, EACCES, EEXIST,
etc.) — branch on that, not the message string.
Combining
import { readFile, writeFile, mkdir } from "node:fs/promises";
import { join } from "node:path";
async function archive(srcFile, outDir) {
await mkdir(outDir, { recursive: true });
const text = await readFile(srcFile, "utf8");
const out = join(outDir, `archive-${Date.now()}.txt`);
await writeFile(out, text);
return out;
}
console.log(await archive("data.txt", "backups")); Concurrency:
// read 3 files in parallel
const [a, b, c] = await Promise.all([
readFile("a.txt", "utf8"),
readFile("b.txt", "utf8"),
readFile("c.txt", "utf8"),
]); Promise.all runs them concurrently — much faster than a serial
loop.