Module F-3·20 min read

The fs module, sync vs async variants, path.join and path.resolve, process.env and process.argv — reading from and writing to the real world.

Module F-3 — Working with Files, Paths, and the Environment

What this module covers: The moment you write a server, a script, or a CLI tool, you need to read files, write logs, parse paths, and pull configuration from the environment. This module covers Node.js's fs module for file I/O, the path module for platform-safe path manipulation, and the process object for environment variables and command-line arguments. These three are in practically every Node.js program you will ever write.


The fs Module

The fs (file system) module is how Node.js reads and writes files on disk. Every operation comes in two flavours: synchronous (blocks until complete) and asynchronous (non-blocking, uses a callback or Promise). We'll cover when to use each.

Reading a File

Async with callback (old style — you will see this in legacy code):

javascript

The callback receives two arguments by convention: err first, then the result. If err is not null, something went wrong. This error-first callback pattern is a Node.js convention you will see everywhere in older code.

Async with Promises (modern style — use this):

javascript

node:fs/promises is the Promise-based version of the fs module, available since Node.js 14. Always prefer this in new code — it works with async/await and avoids callback nesting.

Synchronous (avoid in servers, fine in scripts):

javascript

readFileSync blocks the entire Node.js process until the file is read. In a script that runs once and exits, this is fine. In a server handling concurrent requests, never use it — you will block every other request while one request waits for a file read.

Writing a File

javascript

Common flags:

  • 'w' — write (creates or overwrites)
  • 'a' — append (creates or adds to end)
  • 'wx' — write, but fail if file already exists (useful for "create once" files)

Checking if a File Exists

javascript

Avoid the old fs.existsSync() pattern. It creates a race condition: by the time you act on the result (open the file), another process may have deleted it. The correct pattern is to attempt the operation and handle the error — exactly what try/catch around fs.access does.

Creating Directories

javascript

Listing Directory Contents

javascript

Deleting Files and Directories

javascript

Renaming and Moving Files

javascript

Getting File Metadata

javascript

The path Module

File paths are one of the most common sources of bugs in cross-platform code. On Windows, paths use backslashes (C:\Users\jatin). On macOS and Linux, they use forward slashes (/home/jatin). The path module abstracts this so your code works everywhere.

javascript

path.join — Concatenate path segments safely

javascript

path.join also normalises the result — it collapses double slashes and resolves . and ..:

javascript

path.resolve — Build an absolute path

javascript

Use path.resolve when you need a full absolute path — for example, when passing paths to other modules or when reading files relative to the project root.

path.dirname and path.basename

javascript

__dirname and __filename (CommonJS only)

In CommonJS modules, __dirname is the absolute path of the directory containing the current file, and __filename is the absolute path of the current file itself.

javascript

This is the correct way to load files relative to the current file. Never use relative paths directly with fs — they resolve relative to process.cwd(), which changes depending on where you run node from.

javascript

ESM equivalent of __dirname

In ES Modules, __dirname is not available. Use import.meta.url instead:

javascript

This boilerplate is verbose. Many projects put it in a __dirname.js utility or use a bundler that handles it automatically.

path.parse and path.format

javascript

The process Object

process is a global object — no import needed — that gives you access to the Node.js runtime environment. It is one of the most-used globals in Node.js.

Environment Variables

Environment variables are the correct way to configure a Node.js application. They keep secrets out of source code and allow the same code to behave differently in development, staging, and production.

javascript

Set them when running Node.js:

bash

In practice you use the dotenv package to load a .env file during development:

bash
javascript

We cover dotenv and environment configuration properly in P-6. For now, know that process.env is where you read configuration.

Command-Line Arguments

javascript

For anything beyond trivial argument parsing, use a library like minimist or the built-in util.parseArgs (Node 18+):

javascript

Other Useful process Properties

javascript

Exiting the Process

javascript

Use process.exit(1) when your application detects a fatal configuration error at startup — missing required environment variables, unable to connect to the database, etc. Under normal operation, Node.js exits naturally when there is no more work to do.

Listening for Process Events

javascript

Graceful shutdown is covered in depth in the Practitioner phase. For now, know that SIGTERM is the signal your process receives when Docker or Kubernetes is stopping it, and you should listen for it to clean up open connections before exiting.


Putting It Together: A Practical Script

Here is a small utility that reads a JSON config file, uses environment variables for overrides, and logs the result to a file — combining everything from this module:

javascript

Run it:

bash

Summary

  • fs/promises is the module to use for all file operations in new code. It returns Promises and works with async/await. Avoid callback-style fs and never use sync variants (readFileSync) in servers.
  • path.join for safe path concatenation. path.resolve for absolute paths. Always use these instead of string concatenation.
  • __dirname (CommonJS) or the import.meta.url pattern (ESM) to get the directory of the current file. Never use bare relative paths with fs — they depend on cwd, which is fragile.
  • process.env for configuration and secrets. Read at startup, fail fast if required variables are missing.
  • process.argv for command-line arguments. Slice at index 2. Use util.parseArgs for structured parsing.
  • process.exit(1) for fatal startup errors. Handle SIGTERM for graceful shutdown in long-running processes.

Next: asynchronous JavaScript — callbacks, Promises, async/await, and a first look at how the event loop model means Node.js never has to wait.


Knowledge Check

Why is it considered a bad practice to use fs.existsSync() before reading or writing a file?


Which of the following is the most robust and cross-platform way to construct a file path relative to the current script in a CommonJS module?


What is the primary purpose of process.exit(1) in a Node.js script?

Test your knowledge with more question sets

Sign in to access a wider variety of questions and get notified when new practice sets are added to this module.

Sign in & Register

Discussion

0

Join the discussion

Loading comments...

© 2026 Jatin Jain Saraf (JJS). All rights reserved.