Files and paths

How to use the Node.js fs module with async/await

Learn how to use the Node.js fs module with async/await through the promise-based node:fs/promises API, so you can await calls like fs.readdir() directly.

Node’s filesystem module has a stable promise API in node:fs/promises:

import { readFile, readdir } from 'node:fs/promises'

const posts = await readdir('content')
const configText = await readFile('config.json', 'utf8')
const config = JSON.parse(configText)

The node: prefix makes it explicit that this is a Node built-in rather than a package from node_modules. readFile() returns a Buffer unless you request an encoding such as 'utf8'.

An awaited filesystem failure rejects the promise. Catch errors where you can add useful context or make a specific recovery decision:

async function loadOptionalConfig(path) {
  try {
    return JSON.parse(await readFile(path, 'utf8'))
  } catch (error) {
    if (error.code === 'ENOENT') return {}
    throw new Error(`Cannot load ${path}`, { cause: error })
  }
}

Do not turn every error into an empty value. ENOENT means the path is missing; invalid JSON, permission errors, and I/O failures should remain visible.

Avoid checking with access() before reading. The file can change between the check and the read. Attempt the real operation and handle its result.

Promise-based filesystem work uses Node’s worker pool, so it does not block the event-loop thread while waiting for ordinary file I/O. It is not automatically safe to launch overlapping writes to the same file. Those operations are not synchronized and can overwrite or corrupt the intended result.

Use new URL('./config.json', import.meta.url) when a resource belongs beside an ES module. Use a path relative to process.cwd() when the caller deliberately selects a file from the working directory.

Wrap the entry point so a rejected promise does not become an unhandled rejection:

main().catch((error) => {
  console.error(error)
  process.exitCode = 1
})

Try this on your own: load one valid JSON file, one missing file, and one malformed file. Recover only from ENOENT, preserve the original error as cause for the others, and explain why a preflight existence check would still race. Print the parsed object for the valid file so you know the read succeeded.

Lesson completed