JavaScript Basics: Working with Paths in Node.js
Use Node’s built-in node:path module to assemble paths and extract filenames. These examples run on Linux with Node.js v24.19.0. No packages are needed.
Set up a scratch directory
Run this in Bash, and keep using the same terminal:
You need Bash, a text editor, the mktemp, rm, and rmdir utilities, and Node.js on your PATH. Save source files as UTF-8; their .mjs extension selects ES modules.
start_dir="$PWD"
work_dir="$(mktemp -d)" || exit 1
cd -- "$work_dir" || exit 1Join parts and resolve an absolute path
In your editor, save this as path-build.mjs in the scratch directory:
import assert from 'node:assert/strict';
import path from 'node:path';
const relative = path.join('reports', 'drafts', '..', 'summary.txt');
const absolute = path.resolve('/srv/demo', relative);
const reset = path.resolve('/srv/demo', '/archive', 'summary.txt');
assert.equal(relative, 'reports/summary.txt');
assert.equal(absolute, '/srv/demo/reports/summary.txt');
assert.equal(reset, '/archive/summary.txt');
assert.equal(path.join('', ''), '.');
assert.equal(path.resolve(''), process.cwd());
console.log(relative);
console.log(absolute);
console.log(reset);Run:
node path-build.mjsOutput:
reports/summary.txt
/srv/demo/reports/summary.txt
/archive/summary.txtjoin() combines and normalizes segments. resolve() works from right to left until it has an absolute path; otherwise it uses the current working directory. The explicit /srv/demo base keeps this output predictable. See the official join and resolve reference.
These are lexical operations on strings. They don’t create directories, check existence, or resolve symbolic links. Normalization alone is not a security check for user-supplied paths.
Inspect a filename
Save as path-parts.mjs:
import assert from 'node:assert/strict';
import path from 'node:path';
const file = '/srv/demo/archive.tar.gz';
const parts = path.parse(file);
assert.equal(path.basename(file), 'archive.tar.gz');
assert.equal(path.extname(file), '.gz');
assert.equal(path.extname('.config'), '');
assert.equal(path.extname('report.'), '.');
assert.equal(parts.root, '/');
assert.equal(parts.dir, '/srv/demo');
assert.equal(parts.base, 'archive.tar.gz');
assert.equal(parts.name, 'archive.tar');
assert.equal(parts.ext, '.gz');
console.log(`filename: ${path.basename(file)}`);
console.log(`extension: ${path.extname(file)}`);
console.log(`directory: ${parts.dir}`);
console.log(`name: ${parts.name}`);Run:
node path-parts.mjsOutput:
filename: archive.tar.gz
extension: .gz
directory: /srv/demo
name: archive.tarThe extension is only the final suffix. A leading dot alone doesn’t make .config have an extension. parse() separates the root, directory, filename, name, and extension.
Choose the path convention explicitly
Save as path-platforms.mjs:
import assert from 'node:assert/strict';
import path from 'node:path';
const windowsFile = String.raw`C:\reports\summary.txt`;
assert.equal(path.win32.basename(windowsFile), 'summary.txt');
assert.equal(path.posix.basename(windowsFile), windowsFile);
assert.equal(path.posix.join('/reports', 'summary.txt'), '/reports/summary.txt');
console.log(path.win32.basename(windowsFile));
console.log(path.posix.basename(windowsFile));
console.log(path.posix.join('/reports', 'summary.txt'));Run:
node path-platforms.mjsOutput:
summary.txt
C:\reports\summary.txt
/reports/summary.txtDefault behavior follows the host OS. For paths received from another system, select path.win32 or path.posix. On POSIX, backslashes are ordinary filename characters.
Clean up
Remove only the tutorial files, then remove the empty scratch directory:
rm -- path-build.mjs path-parts.mjs path-platforms.mjs
cd -- "$start_dir" || exit 1
rmdir -- "$work_dir"
unset start_dir work_dirAcknowledgements
This post was created with assistance from AI.