Working with Paths Portably

node:path does string surgery, not disk access. Nothing in it asks whether a path exists; it parses and builds strings by the rules of one platform. The listing runs on Windows, hence the backslashes.

The path functions you will use every dayJavaScript
import path from 'node:path';
const file = path.join('public', 'img', 'logo.png');   // never build paths with +
console.log('join     ', file);
console.log('resolve  ', '...' + path.resolve(file).slice(-24));
console.log('parse    ', path.dirname(file), path.basename(file, '.png'), path.extname(file));
console.log('relative ', path.relative('public/img', 'src/app.js'));
console.log('sep      ', JSON.stringify(path.sep), 'delimiter', path.delimiter);
console.log('posix    ', path.posix.join('public', 'img', 'logo.png'));
console.log('here     ', path.basename(import.meta.dirname));
Output
join      public\img\logo.png
resolve   ...demo\public\img\logo.png
parse     public\img logo .png
relative  ..\..\src\app.js
sep       "\\" delimiter ;
posix     public/img/logo.png
here      demo

join glues segments and normalizes them, so path.join('a', '..', 'b') is b. resolve walks its arguments right to left until it has an absolute path, falling back on process.cwd() — which makes it the wrong tool for finding files that belong to your module, since the current directory is wherever the user ran node. Anchor on import.meta.dirname in ES modules or __dirname in CommonJS and join from there.

Three habits keep path code portable. Never hard-code a separator — path.sep is \ on Windows, and path.delimiter, which separates PATH entries, is ; there and : elsewhere. Use path.posix when the string is a URL path or an object-storage key. And convert a file: URL with fileURLToPath from node:url, never by slicing url.pathname, which leaves a slash before the drive letter.