File System in Node.js


File System in Node.js Interview with follow-up questions

1. What is the File System module in Node.js and why is it important?

The fs module is Node's built-in API for working with the file system — reading and writing files, creating/removing/renaming files and directories, watching for changes, and inspecting metadata and permissions (stat, access, chmod). It's part of core, so no install is needed.

It matters because real applications constantly touch the filesystem: loading config/.env, writing logs, reading templates/assets, handling uploads, generating reports/exports, and streaming large files efficiently.

import { readFile, mkdir, writeFile } from 'node:fs/promises';
await mkdir('out', { recursive: true });
await writeFile('out/report.txt', 'hello');

Points worth making: it offers three APIs — fs/promises (modern, async/await), callbacks, and sync (*Sync, which blocks the loop — use only at startup). Prefer the promise API, import via the node:fs prefix, pair with the path module for cross-platform paths, and use streams (createReadStream/createWriteStream) for large files. In modern Node, filesystem access can also be locked down with the opt-in permission model (--allow-fs-read/--allow-fs-write).

↑ Back to top

Follow-up 1

Can you explain some of the key methods provided by the File System module?

Sure! Here are some of the key methods provided by the File System module:

  • fs.readFile(path, options, callback): Reads the contents of a file asynchronously.

  • fs.writeFile(file, data, options, callback): Writes data to a file asynchronously.

  • fs.readdir(path, options, callback): Reads the contents of a directory asynchronously.

  • fs.mkdir(path, options, callback): Creates a directory asynchronously.

  • fs.unlink(path, callback): Deletes a file asynchronously.

These are just a few examples, and there are many more methods available in the File System module.

Follow-up 2

How does the File System module handle errors?

The File System module in Node.js follows the common error-first callback pattern. When an error occurs during a file system operation, the error is passed as the first argument to the callback function. If no error occurs, the first argument will be null or undefined. It is important to handle these errors properly in your code by checking the first argument of the callback function. You can use conditional statements or try-catch blocks to handle errors and take appropriate actions.

Follow-up 3

What are some use cases for the File System module in Node.js?

The File System module in Node.js has a wide range of use cases. Some common use cases include:

  • Reading and writing configuration files

  • Logging data to files

  • Creating, deleting, and manipulating directories

  • Reading and writing CSV or JSON files

  • Serving static files in web applications

  • Implementing file upload functionality

These are just a few examples, and the File System module can be used in many other scenarios where file-related operations are required.

2. How can you read a file in Node.js using the File System module?

The modern, idiomatic way is the promise-based fs/promises API with async/await and try/catch:

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

try {
  const data = await readFile('path/to/file.txt', 'utf8');
  console.log(data);
} catch (err) {
  console.error('read failed:', err.message);   // e.g. ENOENT
}

If you omit the encoding you get a Buffer; pass 'utf8' to get a string. The classic callback form (fs.readFile(path, 'utf8', (err, data) => …)) still works, and there's a blocking fs.readFileSync for startup-only use — but avoid *Sync in request handlers since it stalls the event loop.

The detail that signals up-to-date knowledge: for large files, don't readFile the whole thing into memory — use fs.createReadStream and process chunks (optionally with pipeline), which keeps memory flat and supports backpressure. Also use the node: import prefix and handle the common ENOENT (file not found) error explicitly.

↑ Back to top

Follow-up 1

What are the differences between the synchronous and asynchronous methods of reading files?

The synchronous method of reading files in Node.js is fs.readFileSync(), while the asynchronous method is fs.readFile(). The main difference between the two is that the synchronous method blocks the execution of the code until the file is read, while the asynchronous method allows the code to continue running without waiting for the file to be read.

Here is an example of using the synchronous method:

const fs = require('fs');

try {
  const data = fs.readFileSync('path/to/file.txt', 'utf8');
  console.log(data);
} catch (err) {
  console.error(err);
}

And here is an example of using the asynchronous method:

const fs = require('fs');

fs.readFile('path/to/file.txt', 'utf8', (err, data) => {
  if (err) throw err;
  console.log(data);
});

Follow-up 2

How would you handle errors while reading a file?

To handle errors while reading a file in Node.js, you can use the error object passed to the callback function of the fs.readFile() method. Here is an example:

const fs = require('fs');

fs.readFile('path/to/file.txt', 'utf8', (err, data) => {
  if (err) {
    console.error(err);
    // Handle the error
  } else {
    console.log(data);
    // Continue with the file data
  }
});

In the callback function, you can check if the err parameter is truthy, indicating an error occurred. You can then handle the error as needed, such as logging it or taking appropriate action.

Follow-up 3

What is the significance of the 'utf8' parameter in the readFile method?

The 'utf8' parameter in the fs.readFile() method is the encoding of the file being read. It specifies that the file should be read as a UTF-8 encoded string. If the encoding parameter is not provided, the file data will be returned as a buffer.

For example, if you want to read a text file and work with its contents as a string, you would pass 'utf8' as the encoding parameter:

const fs = require('fs');

fs.readFile('path/to/file.txt', 'utf8', (err, data) => {
  if (err) throw err;
  console.log(data);
});

3. How can you write to a file in Node.js using the File System module?

Use the promise-based fs/promises API. writeFile creates or overwrites the file:

import { writeFile, appendFile } from 'node:fs/promises';

await writeFile('file.txt', 'Hello, World!');          // overwrite (or create)
await appendFile('log.txt', 'new line\n');             // append
await writeFile('file.txt', 'more', { flag: 'a' });    // append via flag

Things interviewers like you to know:

  • writeFile truncates an existing file; use appendFile or { flag: 'a' } to add to it.
  • It accepts a string or Buffer, with an optional encoding (utf8 default for strings) and mode/flag options.
  • The classic callback form and the blocking writeFileSync exist too; avoid *Sync on hot paths.
  • For large or streamed output, use fs.createWriteStream and pipeline() instead of building a giant string in memory.
  • writeFile isn't atomic — for critical writes, write to a temp file and rename it into place. Wrap calls in try/catch to handle permission/ENOSPC errors.
↑ Back to top

Follow-up 1

What are the differences between writeFile and appendFile methods?

The writeFile and appendFile methods in the Node.js File System module are used to write data to a file, but they have some differences:

  • writeFile method overwrites the file if it already exists, while appendFile method appends the data to the end of the file.
  • writeFile method creates a new file if it doesn't exist, while appendFile method throws an error if the file doesn't exist.
  • writeFile method takes a callback function that is called once the write operation is complete, while appendFile method does not take a callback function.

Follow-up 2

How would you handle errors while writing to a file?

To handle errors while writing to a file in Node.js, you can use the error parameter in the callback function of the writeFile method. Here's an example:

const fs = require('fs');

const data = 'Hello, World!';

fs.writeFile('file.txt', data, (err) => {
  if (err) {
    console.error('Error writing to file:', err);
  } else {
    console.log('File has been written.');
  }
});

In this example, if an error occurs during the write operation, the error parameter will be populated with the error object. You can then handle the error accordingly, such as logging an error message or taking appropriate action.

Follow-up 3

Can you explain the use of flags in the writeFile method?

In the writeFile method of the Node.js File System module, you can specify flags as an optional parameter to control how the file is opened and written. Flags are used to indicate the file system operations that should be performed on the file. Here are some commonly used flags:

  • 'w': Opens the file for writing. If the file doesn't exist, it creates a new file. If the file exists, it truncates the file to zero length.
  • 'a': Opens the file for appending. If the file doesn't exist, it creates a new file.
  • 'r+': Opens the file for reading and writing. The file must exist.

You can combine multiple flags by using the bitwise OR operator (|). For example, to open a file for appending and create it if it doesn't exist, you can use the flag 'a' | 'w'.

4. What are streams in Node.js and how are they related to the File System module?

Streams process data in chunks instead of loading it all into memory, and the fs module exposes file-backed streams so you can read/write large files efficiently:

  • fs.createReadStream(path) — a Readable stream that emits the file's contents chunk by chunk.
  • fs.createWriteStream(path) — a Writable stream you write chunks to.

They connect via pipe/pipeline, which also gives you backpressure (the reader slows down if the writer can't keep up):

import { pipeline } from 'node:stream/promises';
import { createReadStream, createWriteStream } from 'node:fs';
import { createGzip } from 'node:zlib';

await pipeline(
  createReadStream('big.log'),
  createGzip(),
  createWriteStream('big.log.gz')
);

The relationship to summarize: fs provides the file endpoints, and streams provide the chunked transport between them (and to other destinations like an HTTP response or a compressor). This is how you serve large downloads, copy/transform big files, or process logs/CSVs with constant memory — versus readFile/writeFile, which buffer the whole file. Prefer pipeline() over manual .pipe() for automatic error handling and cleanup.

↑ Back to top

Follow-up 1

Can you explain the difference between Readable and Writable streams?

In Node.js, Readable streams are used for reading data from a source, such as a file or a network socket. They emit 'data' events when new data is available to be read. Writable streams, on the other hand, are used for writing data to a destination, such as a file or a network socket. You can write data to a writable stream using the 'write' method. Readable and writable streams can be piped together to create a data flow, where data is read from a readable stream and written to a writable stream.

Follow-up 2

How can you handle stream errors in Node.js?

In Node.js, you can handle stream errors by listening to the 'error' event on the stream object. When an error occurs, the 'error' event is emitted, and you can handle it by attaching a listener to the event. For example, you can use the 'on' method to listen to the 'error' event on a readable or writable stream:

const fs = require('fs');

const readableStream = fs.createReadStream('input.txt');

readableStream.on('error', (error) => {
  console.error('An error occurred:', error);
});

Follow-up 3

What are some use cases for streams in Node.js?

Streams in Node.js have several use cases, including:

  1. Reading and writing large files: Streams allow you to read or write large files in chunks, which is more memory-efficient than loading the entire file into memory.

  2. Processing data in real-time: Streams are useful for processing data in real-time, such as parsing a large JSON file or transforming data from one format to another.

  3. Network communication: Streams can be used for reading or writing data over network sockets, such as handling HTTP requests or responses.

  4. Compressing or decompressing data: Streams can be used for compressing or decompressing data on the fly, such as creating or extracting ZIP files.

These are just a few examples, and streams can be used in many other scenarios where handling large amounts of data efficiently is required.

5. How can you handle file uploads in Node.js?

File uploads arrive as multipart/form-data, which the core body parsers don't handle, so you use dedicated middleware. With Express, multer is the standard:

import express from 'express';
import multer from 'multer';

const upload = multer({
  dest: 'uploads/',
  limits: { fileSize: 5 * 1024 * 1024 },           // cap size (DoS protection)
  fileFilter: (req, file, cb) => cb(null, file.mimetype.startsWith('image/')),
});

app.post('/upload', upload.single('file'), (req, res) => {
  res.json({ stored: req.file.filename });          // req.file / req.files
});

The security/best-practice points that elevate the answer (the original lacked them):

  • Limit file size and validate type (fileFilter + verify real content, not just the extension/MIME).
  • Don't trust originalname — sanitize or generate your own filename to avoid path traversal/overwrites.
  • For scale, stream uploads straight to object storage (S3/GCS) via multer-s3 or presigned URLs rather than local disk; use memory storage only for small files.
  • Alternatives: Busboy (the streaming parser multer is built on) for fine-grained control; Fastify has @fastify/multipart. Always handle errors (size-limit rejections) and consider virus scanning for untrusted uploads.
↑ Back to top

Follow-up 1

What are some security considerations when handling file uploads?

When handling file uploads in Node.js, there are several security considerations to keep in mind:

  1. File type validation: Ensure that only allowed file types are uploaded. You can use file type validation libraries like 'file-type' to check the file type before storing it.

  2. File size limit: Set a limit on the maximum file size that can be uploaded to prevent denial of service attacks. Multer provides options to limit the file size.

  3. File name sanitization: Sanitize the file name to prevent directory traversal attacks. You can use libraries like 'sanitize-filename' to sanitize the file name.

  4. File storage location: Store uploaded files in a secure location outside of the web root directory to prevent direct access.

  5. Virus scanning: Scan uploaded files for viruses using antivirus software or libraries like 'clamscan'.

By implementing these security measures, you can ensure that file uploads are handled safely and securely.

Follow-up 2

How can you limit the size of an uploaded file?

To limit the size of an uploaded file in Node.js, you can use the 'multer' middleware. Multer provides options to set the maximum file size allowed for uploads.

Here is an example of how to limit the file size using Multer:

const express = require('express');
const multer = require('multer');

const app = express();

// Set up Multer
const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, 'uploads/');
  },
  filename: function (req, file, cb) {
    cb(null, file.originalname);
  }
});

const upload = multer({
  storage: storage,
  limits: {
    fileSize: 1024 * 1024 // 1MB
  }
});

// Handle file upload
app.post('/upload', upload.single('file'), (req, res) => {
  res.send('File uploaded successfully');
});

app.listen(3000, () => {
  console.log('Server started on port 3000');
});

In this example, we set the limits.fileSize option to 1MB (1024 * 1024 bytes) to limit the file size. If the uploaded file exceeds the specified limit, Multer will return an error.

Follow-up 3

Can you explain how the 'multer' middleware is used for handling file uploads in Node.js?

The 'multer' middleware is used for handling file uploads in Node.js. It provides an easy way to handle multipart/form-data, which is typically used for file uploads.

To use Multer, you need to follow these steps:

  1. Install Multer using npm:
npm install multer
  1. Require Multer in your Node.js application:
const multer = require('multer');
  1. Set up Multer with a storage destination and filename function:
const storage = multer.diskStorage({
  destination: function (req, file, cb) {
    cb(null, 'uploads/');
  },
  filename: function (req, file, cb) {
    cb(null, file.originalname);
  }
});
  1. Create an instance of Multer with the storage configuration:
const upload = multer({ storage: storage });
  1. Use the Multer middleware to handle file uploads in your route handler:
app.post('/upload', upload.single('file'), (req, res) => {
  // Handle the uploaded file
});

In this example, we set up Multer with a destination folder and a filename function. We then use the upload.single() middleware to handle a single file upload. The file is accessible in the request object as req.file.

Live mock interview

Mock interview: File System in Node.js

Intermediate ~5 min Your own free AI key

Your voice and your AI key never touch our servers; the key stays in this browser and is sent only to Google. Only your round scores are saved to track progress.