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
fsmodule for file I/O, thepathmodule for platform-safe path manipulation, and theprocessobject 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):
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):
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):
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
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
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
Listing Directory Contents
Deleting Files and Directories
Renaming and Moving Files
Getting File Metadata
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.
path.join — Concatenate path segments safely
path.join also normalises the result — it collapses double slashes and resolves . and ..:
path.resolve — Build an absolute path
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
__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.
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.
ESM equivalent of __dirname
In ES Modules, __dirname is not available. Use import.meta.url instead:
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
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.
Set them when running Node.js:
In practice you use the dotenv package to load a .env file during development:
We cover dotenv and environment configuration properly in P-6. For now, know that process.env is where you read configuration.
Command-Line Arguments
For anything beyond trivial argument parsing, use a library like minimist or the built-in util.parseArgs (Node 18+):
Other Useful process Properties
Exiting the Process
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
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:
Run it:
Summary
fs/promisesis the module to use for all file operations in new code. It returns Promises and works withasync/await. Avoid callback-stylefsand never use sync variants (readFileSync) in servers.path.joinfor safe path concatenation.path.resolvefor absolute paths. Always use these instead of string concatenation.__dirname(CommonJS) or theimport.meta.urlpattern (ESM) to get the directory of the current file. Never use bare relative paths withfs— they depend oncwd, which is fragile.process.envfor configuration and secrets. Read at startup, fail fast if required variables are missing.process.argvfor command-line arguments. Slice at index 2. Useutil.parseArgsfor structured parsing.process.exit(1)for fatal startup errors. HandleSIGTERMfor 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.
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 & RegisterDiscussion
0Join the discussion