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

By

Published · Updated

Tagged: Node.js

Learn how to use node:fs/promises with async/await, await readFile and readdir, and handle missing files without blocking the event loop.

Node.js built-in modules famously started out callback-based, not promise-based.

This is for historical reasons. Those modules were created before promises were a thing.

We’ve had util.promisify() for quite some time, but when I wrote this post I had just found out Node also provides a promise-based API in the fs module.

I thought it was new but it’s been introduced in Node.js 10 (2018, it’s been a while!).

Here’s how to use it, on any current Node.js (26 or 24, both LTS).

Import the promise API, not node:fs:

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

The node: prefix marks this as a built-in module. import * as fs from 'node:fs/promises' works too, then you call fs.readdir().

Now you can await the methods you already know:

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

await writeFile(
  'content/hello.txt',
  'Hello from Node\n',
  'utf8'
)

readFile() returns a Buffer unless you pass an encoding. Pass 'utf8' when you want a string. Forget it and JSON.parse() throws, because you handed it bytes.

Handle errors

An awaited filesystem call rejects when it fails. Catch the error where you can recover, and let the rest bubble:

async function loadOptionalConfig(path) {
  try {
    return JSON.parse(await readFile(path, 'utf8'))
  } catch (error) {
    if (error.code === 'ENOENT') {
      return {}
    }
    throw error
  }
}

ENOENT means the path is missing. Invalid JSON, permission errors, and disk failures should stay visible. Do not turn every error into an empty object.

Avoid access() then readFile(). The file can disappear between the two calls. Attempt the read and handle its result.

Do not mix the two APIs

This looks like it should work. It does not:

import fs from 'node:fs'

const text = await fs.readFile('config.json', 'utf8')

node:fs is the callback API. readFile() there expects a callback and returns undefined. You want node:fs/promises.

The promise methods do not block the event loop while the disk works. The Sync methods on node:fs do. Use those only in a short startup script.

Other built-ins

Back then I wasn’t sure this would be ported to other native modules. It was. Node now ships a /promises export for several built-ins, including node:dns/promises, node:timers/promises, node:stream/promises and node:readline/promises. Check the Node docs for one before you fall back to util.promisify().

Want me to talk about your product? You can sponsor this site.

Related posts about node: