NodeJSWorking with Directories

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

JS
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 })
`recursive: true` is almost always what you want
Plain `mkdir` throws `ENOENT` if any parent doesn't exist, and `EEXIST` if the target already does. `{ recursive: true }` fixes both — it creates intermediate directories (like `mkdir -p`) and silently succeeds if the path is already there. Use it for any "ensure this directory exists" need.
Listing contents

JS
import { readdir } from 'node:fs/promises'

const names = await readdir('.')                 // → ['a.txt', 'src', 'b.js']
console.log(names)
[ 'a.txt', 'src', 'b.js' ]
`readdir` returns NAMES, not paths — and isn't recursive by default
The strings are *base names*, not full paths. To use them with other `fs` calls you must `path.join` them onto the directory: `join(dir, name)`. And `readdir` lists only the immediate level — pass `{ recursive: true }` (Node 18.17+) for a deep listing, or walk manually.
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():

JS
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)
}
`withFileTypes` avoids an N+1 stat storm
Without it, classifying entries means calling `stat` once per file — slow for large directories. `withFileTypes: true` returns the type info from the single `readdir` syscall, so it's both faster and race-free.
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):

JS
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

JS
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)
`rmdir` on a non-empty dir throws ENOTEMPTY
`rmdir` deletes only empty directories. For one with contents, use `rm(path, { recursive: true })`. (Recursive `rmdir` is deprecated in favor of `rm`.) As with all recursive deletes, double-check the path — `{ recursive: true, force: true }` is unstoppable. See [Deleting & Renaming Files](/nodejs/deleting-files).
Useful directory helpers

JS
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-Xa3Bc1
`mkdtemp` for safe scratch space
`mkdtemp` creates a uniquely-named temp directory (appending random characters to your prefix), avoiding collisions between concurrent runs. Pair it with `os.tmpdir()` for the right per-platform location, and `rm(dir, { recursive: true })` to clean up when done.
Operation reference

Goal

Call

Create (with parents)

mkdir(path, { recursive: true })

List immediate entries

readdir(path)

List with types

readdir(path, { withFileTypes: true })

List recursively

readdir(path, { recursive: true })

Remove empty dir

rmdir(path)

Remove tree

rm(path, { recursive: true, force: true })

Unique temp dir

mkdtemp(join(tmpdir(), "prefix-"))

Next
Inspect a file's size, type, and timestamps: [File Stats & Metadata](/nodejs/file-stats).