Question

Writing Files in Node.js with the fs Module

javascriptnode.jsfileexpressfs

Question

How can I write data to a file in a Node.js application? I would like to save text or other content to a file, but I have not found a working approach yet.

Short Answer

You will learn how Node.js writes files with its built-in fs module. This includes callback-based, Promise-based, synchronous, append, and JSON file-writing examples, plus practical error-handling guidance.

Concept

Node.js does not write files directly from a variable or console.log(). Instead, it uses the built-in file system module, named fs.

Writing a file involves three pieces of information:

  1. The path: where the file should be saved, such as ./notes.txt.
  2. The data: the text, Buffer, or serialized JSON to save.
  3. An error strategy: what to do if the path is invalid, permissions are missing, or storage fails.

The modern Promise-based API is usually the clearest choice:

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

await fs.writeFile('./notes.txt', 'Hello from Node.js!', 'utf8');

writeFile() creates the file when it does not exist. By default, it replaces all existing contents when the file already exists.

File writing is important for scripts that generate reports, export data, save application settings, create logs, or process uploaded content. Because disk operations can take time, Node.js commonly uses asynchronous APIs so other work can continue while the operating system writes the file.

Mental Model

Think of a file path as a street address and file data as a package.

  • ./notes.txt tells Node.js where to deliver the package.
  • 'Hello' is what goes into the package.
  • writeFile() is the delivery service.

If a package is delivered to an address that already has a package, writeFile() normally removes the old one and leaves the new one. If you want to add another package without removing the first, use appendFile() instead.

Take Quiz

Syntax and Examples

Use the Promise-based fs API with async/await:

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

async function saveMessage() {
  try {
    await fs.writeFile('./message.txt', 'Hello, file system!\n', 'utf8');
    console.log('File saved successfully.');
  } catch (error) {
    console.error('Could not save the file:', error.message);
  }
}

saveMessage();

fs.writeFile(path, data, encoding) accepts:

  • path: a string such as './message.txt'
  • data: text, a Buffer, or other supported binary data
  • encoding: use 'utf8' when writing normal text

To save a JavaScript object, first convert it to JSON:

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

const settings = {
  theme: 'dark',
  notifications: true
};

async function saveSettings() {
  const json = JSON.stringify(settings, null, 2);
  await fs.writeFile('./settings.json', json, 'utf8');
}

saveSettings().catch(console.error);

The null, 2 arguments format the JSON with indentation, making the file easier for people to read.

Step by Step Execution

Consider this code:

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

async function createReport() {
  const report = 'Users: 24\nActive users: 18\n';
  await fs.writeFile('./report.txt', report, 'utf8');
  console.log('Report created');
}

createReport().catch(console.error);

Execution flow:

  1. require('node:fs/promises') loads Node.js's Promise-based file system functions.
  2. createReport() is called and starts running.
  3. The report variable stores a string containing two lines. \n means a new line.
  4. fs.writeFile() asks the operating system to write that string to report.txt in the current working directory.
  5. await pauses this async function until writing succeeds or fails; it does not block Node.js from handling other asynchronous work.
  6. If writing succeeds, console.log() prints Report created.
  7. If writing fails, the Promise rejects and .catch(console.error) displays the error.

After a successful run, report.txt contains:

Users: 24
Active users: 18

Real World Use Cases

Common uses of file writing include:

  • Report generation: create CSV, text, or JSON reports from database data.
  • Configuration tools: save local settings, feature flags, or development configuration.
  • Build scripts: generate source files, manifests, documentation, or static assets.
  • Data exports: let an administrator export users, orders, or analytics as a downloadable file.
  • Command-line tools: save processed images, converted data, or command output.
  • Logging: append application events to a log file, usually with a logging library in larger services.

For example, a script can save a CSV export:

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

const csv = 'name,email\nAda,ada@example.com\nLinus,linus@example.com\n';

fs.writeFile('./users.csv', csv, 'utf8')
  .then(() => console.log('CSV exported'))
  .catch(console.error);
Take Quiz

Real Codebase Usage

In production code, developers usually make file writing predictable and safe.

Build paths explicitly

Avoid depending on whichever directory happened to start the process. Use path.join() and, where appropriate, a known application directory.

const path = require('node:path');
const fs = require('node:fs/promises');

const outputPath = path.join(process.cwd(), 'output', 'report.txt');

Create parent directories first

writeFile() creates the file, but it does not create missing parent folders.

await fs.mkdir(path.dirname(outputPath), { recursive: true });
await fs.writeFile(outputPath, 'Report data', 'utf8');

Validate data before saving

A guard clause prevents writing invalid content.

async function saveUser(user) {
  if (!user?.email) {
    throw new Error('A user email is required.');
  }

  await fs.writeFile('./user.json', JSON.stringify(user, null, 2), 'utf8');
}

Append when preserving existing content matters

await fs.appendFile('./app.log', `${new Date().toISOString()} Server started\n`, 'utf8');

Handle failures at the boundary

A function that writes a file should either handle a useful error or allow the caller to handle it. Do not silently ignore rejected Promises.

Common Mistakes

Forgetting to wait for an asynchronous write

This code starts the write but immediately prints the success message:

// Problematic
fs.writeFile('./note.txt', 'Hello');
console.log('Saved');

Use await inside an async function, or use .then()/.catch():

await fs.writeFile('./note.txt', 'Hello', 'utf8');
console.log('Saved');

Assuming writeFile() appends

await fs.writeFile('./log.txt', 'First line\n', 'utf8');
await fs.writeFile('./log.txt', 'Second line\n', 'utf8');

The final file contains only Second line. Use appendFile() to add content:

await fs.appendFile('./log.txt', 'Second line\n', 'utf8');

Writing an object without JSON serialization

// Incorrect for JSON output
await fs.writeFile('./user.json', { name: 'Ada' });

Convert the object first:

await fs.writeFile('./user.json', JSON.stringify({ name: 'Ada' }, null, 2), 'utf8');

Forgetting that parent folders must exist

await fs.writeFile('./exports/daily/report.txt', 'Data', 'utf8');

This fails if exports/daily does not exist. Create it with fs.mkdir(directory, { recursive: true }) first.

Ignoring errors

Disk operations can fail because of permissions, invalid paths, full disks, or missing folders. Use try/catch or .catch().

Using synchronous file writes in a server request path

writeFileSync() blocks Node.js while the disk operation finishes. It can be fine in short setup scripts, but asynchronous writeFile() is generally a better choice in web servers and concurrent applications.

Comparisons

MethodBehaviorBest use
fs.writeFile()Asynchronously creates or replaces a fileNormal application and server code
fs.appendFile()Asynchronously adds data to the end of a fileLogs or incremental text output
fs.writeFileSync()Creates or replaces a file while blocking executionSmall scripts, setup code, simple tooling
fs.createWriteStream()Writes data in chunksLarge files or continuous output

Callback API vs Promise API

StyleExampleNotes
Callbackfs.writeFile(path, data, callback)Older Node.js style; still supported
Promisesawait fs.writeFile(path, data)Clear control flow with async/await
Syncfs.writeFileSync(path, data)Simple, but blocks the event loop

Callback example:

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

fs.writeFile('./note.txt', 'Hello', 'utf8', (error) => {
  if (error) {
    console.error(error);
    return;
  }

  console.log('Saved');
});

For new code, node:fs/promises is often easier to read and compose with other asynchronous operations.

Take Quiz

Cheat Sheet

const fs = require('node:fs/promises');
TaskCode
Write or replace textawait fs.writeFile('./file.txt', 'Text', 'utf8')
Append textawait fs.appendFile('./file.txt', 'More text\n', 'utf8')
Write formatted JSONawait fs.writeFile('./data.json', JSON.stringify(data, null, 2), 'utf8')
Create a folderawait fs.mkdir('./output', { recursive: true })
Handle an errortry { await ... } catch (error) { ... }

Key rules:

  • writeFile() overwrites an existing file by default.
  • Parent directories must already exist unless you create them with mkdir().
  • Use 'utf8' for text files.
  • Use JSON.stringify() before writing JavaScript objects as JSON.
  • Prefer asynchronous Promise APIs for application code.
  • Use path.join() for portable paths instead of manually joining path strings.

FAQ

Does fs.writeFile() create a file if it does not exist?

Yes. It creates the file when its parent directory already exists.

Does fs.writeFile() overwrite an existing file?

Yes. Its default behavior is to replace the file's existing contents. Use fs.appendFile() to add content instead.

How do I write JSON to a file in Node.js?

Convert the value using JSON.stringify(value, null, 2), then pass that string to writeFile().

Why do I get an ENOENT error when writing a file?

Usually, part of the path does not exist. Create the parent directory with await fs.mkdir(directory, { recursive: true }).

Should I use writeFileSync() or writeFile()?

Prefer asynchronous writeFile() in applications and servers. Use writeFileSync() only when blocking execution is acceptable, such as a short one-time script.

Can I write a file using require('fs') instead of require('node:fs/promises')?

Yes. fs provides callback and synchronous methods. node:fs/promises provides Promise-returning methods designed for async/await.

Where is ./file.txt saved?

It is resolved from the process's current working directory, often the directory from which you ran node. Use an absolute or constructed path when location must be explicit.

Take Quiz

Mini Project

Description

Build a small report exporter that saves a list of tasks to a formatted JSON file. This mirrors a common backend or command-line task: turning in-memory application data into a file that another person or system can use.

Goal

Create an output/tasks.json file containing a formatted JSON task report.

Requirements

Create an array containing at least three task objects. Create an output directory if it is missing. Save the task data as readable JSON in output/tasks.json. Display a success message that includes the saved file path. Handle file-writing errors without crashing silently.

Take Quiz

Keep learning

Related questions

View more