fs.readFile()
Reads a whole file asynchronously and hands the contents to a callback.
fs.readFile(path[, options], callback)- Added in Node.js v0.1.29
Parameters
| Parameter | Type | Description |
|---|---|---|
$path | <string> | <Buffer> | <URL> | <integer> | Filename or file descriptor. A relative path resolves against process.cwd(), not against the file containing the code. |
$optionsoptional | <Object> | <string>= { encoding: null, flag: 'r' } | A string is treated as the encoding. As an object: encoding <string>|<null> (default null), flag <string> (default 'r'), signal <AbortSignal> to abort an in-progress read, and buffer <Buffer>|<TypedArray>|<DataView>|<Function> to read into a caller-supplied buffer (added in v26.4.0). |
$callback | <Function> | Called as (err, data). err is <Error> or <AggregateError>; data is <string> when an encoding was given, otherwise <Buffer>. When err is set, data is undefined. |
Return value
undefinedNothing. The contents arrive as the second argument to the callback, as a string when an encoding is given and a Buffer otherwise.
Errors
Reports failures such as ENOENT, EACCES and EISDIR through the callback's err argument; it throws synchronously only when path, options or callback have an invalid type.
Changelog
- PHP v26.4.0Added support for the buffer option, letting you read into a buffer you already own.
- PHP v16.0.0The error passed to the callback may be an AggregateError when more than one error occurs (for example a read failure plus a close failure).
- PHP v15.2.0, v14.17.0options may include an AbortSignal to abort an ongoing read.
- PHP v10.0.0The callback parameter is no longer optional; omitting it throws a TypeError at runtime.
- PHP v7.6.0path may be a WHATWG URL object using the file: protocol.
- PHP v5.0.0path may be a file descriptor.
fs.readFile() opens a path, reads it to the end, closes it, then hands the entire contents to your callback. Reach for it when the file is configuration, a template, a fixture, or anything else small enough to sit in memory comfortably.
Encoding decides the type you get
With no encoding the callback receives a Buffer. Pass 'utf8' as the second argument, or { encoding: 'utf8' }, and you get a string instead. Almost every “why is my file printing <Buffer 68 65 6c>” question is that missing argument.
When to use something else
- Files of more than a few megabytes: buffering the whole thing spikes memory. Stream with
fs.createReadStream(). - Files above
buffer.constants.MAX_LENGTH, which fail outright rather than truncating. - One-shot startup reads in a script, where the synchronous twin is simpler and the blocking cost is irrelevant.
Things that bite
Check err before touching data — on failure data is undefined, so data.trim() becomes a confusing TypeError that hides the real ENOENT. Reads are not atomic against a concurrent writer either: if another process truncates the file mid-read you get a short result, not an error. And a relative path resolves against the process working directory, which is why the same code works from the project root and fails from a subdirectory.
Example
When to use it
- Load a JSON config file at startup and parse it before booting a server.
- Read an HTML or Markdown template into memory to render a response.
- Slurp a small fixture file inside a test and assert on its contents.
Frequently asked questions
Why does fs.readFile return a Buffer instead of a string?
Because no encoding was supplied — the default is null, which means raw bytes. Pass 'utf8' as the second argument, or use { encoding: 'utf8' }, to get a string, or call buf.toString('utf8') on the Buffer yourself.
How do I read a file line by line instead of all at once?
Do not use fs.readFile for that; pair fs.createReadStream() with the readline module, which yields one line at a time without holding the whole file in memory. fs.readFile always buffers the entire file before your callback runs.
What is the maximum file size fs.readFile can handle?
It is capped by buffer.constants.MAX_LENGTH, around 4 GiB on 64-bit builds, but you will hit memory pressure long before that. For anything over a few megabytes, stream the file instead of buffering it.
More examples
No encoding gives you a Buffer
The same four bytes come back as a Buffer because no encoding was passed. The output shows Buffer.isBuffer is true, the byte length is 4, and you can decode it yourself with toString('hex') or toString('utf8') to get 'Node'.
const fs = require('node:fs');
fs.writeFileSync('bytes.bin', Buffer.from([0x4e, 0x6f, 0x64, 0x65]));
fs.readFile('bytes.bin', (err, buf) => {
if (err) throw err;
console.log('is Buffer:', Buffer.isBuffer(buf));
console.log('length:', buf.length);
console.log('hex:', buf.toString('hex'));
console.log('as text:', buf.toString('utf8'));
fs.unlinkSync('bytes.bin');
});Handling a missing file
Nothing is thrown into the surrounding scope: the failure arrives as err.code 'ENOENT' with err.syscall 'open'. The last line shows data is undefined on the error path, which is why reading it before checking err produces a misleading TypeError.
const fs = require('node:fs');
fs.readFile('definitely-not-here.txt', 'utf8', (err, data) => {
if (err) {
console.log('code:', err.code);
console.log('syscall:', err.syscall);
console.log('data is:', data);
return;
}
console.log(data);
});Read a JSON file with the promise API
fs/promises exposes the same read without a callback, so it composes with await and try/catch. The output shows the parsed values 8080 and false, then confirms the temporary file was removed.
import fs from 'node:fs';
import fsp from 'node:fs/promises';
async function main() {
await fsp.writeFile('config.json', JSON.stringify({ port: 8080, debug: false }));
const raw = await fsp.readFile('config.json', 'utf8');
const config = JSON.parse(raw);
console.log('port:', config.port);
console.log('debug:', config.debug);
await fsp.unlink('config.json');
console.log('cleaned up:', fs.existsSync('config.json'));
}
main();
Discussion