Published Sep 16, 2025 ⦁ Updated Oct 11, 2026 ⦁ 10 min read
Node.js readFile and readFileSync: Read Files in Node

Node.js readFile and readFileSync: Read Files in Node

To read a file in Node.js, call await readFile('notes.txt', 'utf8') from node:fs/promises. It returns the whole file as a string. Use readFileSync from node:fs only in startup code or CLI scripts, where blocking doesn't matter. For large files, or to read a file line by line, use a stream with readline instead, because readFile loads the entire file into memory.

The examples below were run on Node.js 26 and use only APIs that also exist in the current LTS releases, Node 24 and 22 (as of October 2026). All references point to the official Node.js fs documentation.

The three ways to call readFile

API Import Blocks the event loop? Errors arrive as Use it for
readFile (promise) node:fs/promises No rejected promise (try/catch) Most code
readFile (callback) node:fs No first callback argument err Older code bases
readFileSync node:fs Yes thrown exception Startup config, CLI scripts

All three take the same arguments: a path (string, Buffer, file: URL, or file descriptor) and an optional encoding string or options object { encoding, flag }. The async versions also accept signal.

Node.js readFile with async/await (recommended)

// read.mjs
import { readFile } from 'node:fs/promises';

try {
  const text = await readFile('notes.txt', 'utf8');
  console.log(text);
} catch (err) {
  console.error(`Could not read file: ${err.message}`);
}

Run it with node read.mjs. Top-level await works in ES modules (.mjs files, or .js with "type": "module" in package.json).

In CommonJS, wrap the call in an async function:

// read.cjs
const { readFile } = require('node:fs/promises');

async function main() {
  const text = await readFile('notes.txt', 'utf8');
  console.log(text);
}

main().catch((err) => console.error(err.message));

Always pass an encoding if you want text

Without an encoding, you get a Buffer (raw bytes), not a string:

const buf = await readFile('notes.txt');
console.log(buf);            // <Buffer 68 65 6c 6c 6f 0a 77 6f 72 6c 64 0a>
console.log(buf.toString()); // 'hello\nworld\n' (decodes as UTF-8)

'utf8' and { encoding: 'utf8' } are equivalent. Leave the encoding off on purpose when reading binary files such as images, PDFs or zip files, and work with the Buffer.

fs.readFile with a callback

This is the original API, still fully supported:

const fs = require('node:fs');

fs.readFile('notes.txt', 'utf8', (err, data) => {
  if (err) {
    console.error(err.message);
    return;
  }
  console.log(data);
});

console.log('This line prints first');

The callback runs later, so code after fs.readFile runs before the file contents arrive. That surprises most beginners. Since Node 10 the callback is mandatory; leaving it out throws a TypeError.

readFileSync: synchronous file reading

const fs = require('node:fs');

const text = fs.readFileSync('notes.txt', 'utf8');
console.log(text);

readFileSync returns the contents directly and throws on failure, so wrap it in try/catch if the file may be missing:

let config = {};
try {
  config = JSON.parse(fs.readFileSync('config.json', 'utf8'));
} catch (err) {
  if (err.code !== 'ENOENT') throw err; // ignore "file not found" only
}

When is readFileSync fine? At program startup, in build scripts, and in CLI tools, where nothing else is waiting. When is it wrong? Inside a web server's request handler. While the file is being read, the server cannot answer any other request.

Read a file relative to your script, not your terminal

A relative path like 'notes.txt' is resolved against process.cwd(), the directory you ran node from, not the folder the script lives in. Run node src/read.mjs from the project root, and Node looks for ./notes.txt in the root, not in src/.

To read a file next to the script:

// ES modules
import { readFile } from 'node:fs/promises';
const text = await readFile(new URL('./notes.txt', import.meta.url), 'utf8');
// CommonJS
const path = require('node:path');
const text = require('node:fs').readFileSync(path.join(__dirname, 'notes.txt'), 'utf8');

In ES modules on current Node versions, import.meta.dirname is also available as the equivalent of __dirname.

Read a JSON file

import { readFile } from 'node:fs/promises';

const config = JSON.parse(await readFile('config.json', 'utf8'));
console.log(config.port); // 3000

JSON.parse throws a SyntaxError on invalid JSON, so a single try/catch around both calls covers missing files and broken files. In ES modules you can also write import config from './config.json' with { type: 'json' };, but that file is loaded once and cached, so use readFile when the file can change while the program runs.

Node.js read file line by line

readFile loads the whole file at once. For logs, CSVs or anything large, read it line by line with a stream. This is the pattern from the readline docs:

import { createReadStream } from 'node:fs';
import { createInterface } from 'node:readline';

const rl = createInterface({
  input: createReadStream('access.log'),
  crlfDelay: Infinity, // treat \r\n as a single line break
});

let count = 0;
for await (const line of rl) {
  if (line.includes(' 500 ')) count++;
}
console.log(`${count} server errors`);

Memory use stays small no matter how big the file is. A shorter alternative is filehandle.readLines():

import { open } from 'node:fs/promises';

const file = await open('access.log');
for await (const line of file.readLines()) {
  console.log(line);
}

Common errors and how to fix them

Error code Meaning Fix
ENOENT No such file Check the path, and remember it is relative to process.cwd()
EISDIR The path is a directory Use readdir for directories
EACCES / EPERM No permission Check file permissions or run as the right user
ERR_FS_FILE_TOO_LARGE File is over 2 GiB Use createReadStream; readFile has a 2 GiB limit

Check err.code rather than parsing err.message. The code is stable across versions and platforms.

Performance and limitations

  • readFile buffers the entire file. The docs say to prefer fs.createReadStream() "to minimize memory costs" (fs.readFile).
  • Async readFile reads in chunks (512 KiB per read for regular files), letting the event loop run between chunks. That makes it friendlier to a busy server, but slightly slower than one big read.
  • Aborting with AbortSignal stops Node's internal buffering, but not an operating-system read already in progress.
  • Reading from a file descriptor starts at the descriptor's current position, and Node does not close it for you.

Can JavaScript read files in the browser?

Not with fs. That module exists only in Node.js (and in compatible runtimes such as Deno and Bun). In the browser, the user must pick a file, and you read it with the File API:

// <input type="file" id="picker"> in your HTML
document.querySelector('#picker').addEventListener('change', async (event) => {
  const file = event.target.files[0];
  const text = await file.text();
  console.log(text);
});

Older code uses FileReader, which does the same thing with events instead of promises. To load a file from your own server, use fetch('/data.json').

If you are debugging a script that reads the wrong file, our Node.js debugging guide shows how to step through it. You can find editors and CLI helpers in DevHunt's IDE tools and CLI tools categories.

FAQ

What is the difference between readFile and readFileSync?

readFile is asynchronous: it returns a promise (or calls a callback), and your program keeps running while the file loads. readFileSync blocks until the file is read and returns the contents directly. Both read the whole file into memory and accept the same path and encoding arguments.

Why does fs.readFile return a Buffer instead of a string?

Because you didn't pass an encoding. Call readFile(path, 'utf8') to get a string, or call .toString('utf8') on the Buffer.

How do I read a file synchronously in Node.js?

const text = require('node:fs').readFileSync('file.txt', 'utf8'); In an ES module, use import { readFileSync } from 'node:fs';. Wrap it in try/catch, because it throws if the file is missing.

Should I import from 'fs' or 'node:fs'?

Both work. The node: prefix makes it explicit that you mean the built-in module, not an npm package named fs, and it is the form used throughout the current Node.js docs.

How do I read a large file in Node.js?

Don't use readFile for files that are hundreds of megabytes or more, and it fails outright above 2 GiB. Use createReadStream, with readline if you need lines, to process the file in chunks.