Q&A
How to list files present in a directory using Node.js
Overview
Reading a directory is a common file-system task - building file explorers, processing uploads, scanning for config files. Node's fs module offers callback, promise, and sync variants. Modern code uses the promise API with async/await. The key refinements are distinguishing files from directories and recursing into subfolders.
graph TD
A[List a directory] --> B["fs.promises.readdir(dir) - preferred async"]
A --> C["fs.readdir(dir, cb) - callback"]
A --> D["fs.readdirSync(dir) - blocking"]
B --> E["{ withFileTypes: true } → Dirent: isFile()/isDirectory()"]
E --> F[Recurse into subdirectories]
style B fill:#1e1b4b,color:#fff
Promise API (recommended)
const fs = require("fs/promises");
async function list(dir) {
const entries = await fs.readdir(dir); // array of names (strings)
console.log(entries); // ['a.txt', 'images', 'b.js']
}
list("./mydir");
Clean with async/await and non-blocking. (import { readdir } from "fs/promises" in ESM.)
Callback API
const fs = require("fs");
fs.readdir("./mydir", (err, files) => {
if (err) throw err;
console.log(files);
});
Synchronous API (scripts/startup only)
const fs = require("fs");
const files = fs.readdirSync("./mydir"); // blocks the event loop
Fine for CLI scripts or startup config, but avoid in servers - it blocks all other work while reading.
Files vs Directories: withFileTypes
By default you get just names. Pass { withFileTypes: true } to get Dirent objects that distinguish entry types:
const fs = require("fs/promises");
async function listDetailed(dir) {
const entries = await fs.readdir(dir, { withFileTypes: true });
for (const entry of entries) {
if (entry.isDirectory()) console.log("DIR :", entry.name);
else if (entry.isFile()) console.log("FILE:", entry.name);
}
}
This avoids a separate fs.stat() call per entry, so it's more efficient.
Full Paths with path.join
readdir returns names relative to the directory, not full paths. Build them with path:
const path = require("path");
const fs = require("fs/promises");
const dir = "./mydir";
const names = await fs.readdir(dir);
const fullPaths = names.map(name => path.join(dir, name));
Recursive Listing (walk subdirectories)
const fs = require("fs/promises");
const path = require("path");
async function walk(dir) {
const entries = await fs.readdir(dir, { withFileTypes: true });
const files = [];
for (const entry of entries) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) files.push(...await walk(full)); // recurse
else files.push(full);
}
return files;
}
Modern Node (v18.17+/v20+) also supports a built-in recursive option: fs.readdir(dir, { recursive: true }).
Filtering
const jsFiles = (await fs.readdir(dir)).filter(f => f.endsWith(".js"));
For glob-style matching across trees, the glob package is common.
Why This Matters in Interviews
Node.js interviews use this to check fs familiarity and async discipline. Key points: prefer the promise API (fs/promises + await) over callbacks, and avoid readdirSync on servers (it blocks the event loop); use { withFileTypes: true } to distinguish files from directories without extra stat calls; build full paths with path.join (names are relative); and recurse (or use the built-in recursive option) for nested trees. Error handling (try/catch or callback err) is expected.
Common Pitfalls
- Using
readdirSyncin request handlers, blocking the event loop. - Treating returned names as full paths (they're relative - use
path.join). - Forgetting
withFileTypesand doing an extrastatper entry (slower, racier). - Not handling errors (missing directory →
ENOENT). - Hand-rolling recursion when the built-in
{ recursive: true }(recent Node) suffices.
References
- Node.js
fs.readdir: https://nodejs.org/api/fs.html#fspromisesreaddirpath-options - Node.js
path: https://nodejs.org/api/path.html