Working with Directories
Directories are managed with their own fs functions: mkdir to create, readdir to list, rmdir/rm to remove. The everyday tasks — "make this path if it doesn't exist", "list everything in here", "walk the whole tree" — each have an idiomatic, gotcha-free way to do them, which this page lays out.
Creating directories
import { mkdir } from 'node:fs/promises'
// ✗ Fails with ENOENT if a parent is missing:
await mkdir('a/b/c')
// ✓ Creates the whole chain, and is a no-op if it already exists:
await mkdir('a/b/c', { recursive: true })Listing contents
import { readdir } from 'node:fs/promises'
const names = await readdir('.') // → ['a.txt', 'src', 'b.js']
console.log(names)[ 'a.txt', 'src', 'b.js' ]
Files vs subdirectories: withFileTypes
To know whether each entry is a file or a directory without a separate stat call per entry, pass withFileTypes: true — you get Dirent objects with isFile()/isDirectory():
import { readdir } from 'node:fs/promises'
const entries = await readdir('.', { withFileTypes: true })
for (const entry of entries) {
if (entry.isDirectory()) console.log('📁', entry.name)
else if (entry.isFile()) console.log('📄', entry.name)
}Walking a tree recursively
A classic recursive walk — descend into subdirectories, collect files. (For one-liners, readdir(dir, { recursive: true }) now exists, but writing the walk teaches the pattern and gives you control):
import { readdir } from 'node:fs/promises'
import { join } from 'node:path'
async function* walk(dir) {
for (const entry of await readdir(dir, { withFileTypes: true })) {
const full = join(dir, entry.name)
if (entry.isDirectory()) yield* walk(full) // recurse
else yield full // emit file path
}
}
for await (const file of walk('src')) {
console.log(file) // every file under src/
}Removing directories
import { rmdir, rm } from 'node:fs/promises'
await rmdir('empty-dir') // only works if EMPTY
await rm('full-dir', { recursive: true, force: true }) // delete tree (rm -rf)Useful directory helpers
import { mkdtemp } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
// A unique temp directory — perfect for scratch work you'll clean up:
const dir = await mkdtemp(join(tmpdir(), 'myapp-'))
console.log(dir) // e.g. /tmp/myapp-Xa3Bc1Operation reference
Goal | Call |
|---|---|
Create (with parents) |
|
List immediate entries |
|
List with types |
|
List recursively |
|
Remove empty dir |
|
Remove tree |
|
Unique temp dir |
|