path
The node:path module is the cross-platform way to manipulate file paths — joining, resolving, parsing, normalising. Use it instead of string concatenation and your code stops breaking on Windows where slashes go the wrong way.
join, resolve, parse, normalise, sep
EXAMPLE
import path from 'node:path';
import { fileURLToPath } from 'node:url';
// 1) Joining — system-aware separators
path.join('src', 'utils', 'date.ts') // 'src/utils/date.ts' (POSIX) or 'src\\utils\\date.ts' (Windows)
path.join('/etc/', '/passwd') // '/etc/passwd' — collapses doubled slashes
path.join('/foo', '../bar') // '/bar'
path.join('a', '', 'b') // 'a/b'
// 2) Resolve — produce an absolute path
path.resolve('src/index.ts') // '/home/me/project/src/index.ts'
path.resolve('/foo', 'bar', 'baz') // '/foo/bar/baz'
path.resolve('/foo', '/abs', 'rel') // '/abs/rel' — absolute resets the chain
// resolve walks RIGHT to LEFT prepending segments until absolute.
// 3) Parse + format — round-trip
const p = path.parse('/home/mara/photo.jpg');
// { root: '/', dir: '/home/mara', base: 'photo.jpg', name: 'photo', ext: '.jpg' }
path.format({ dir: '/home/mara', base: 'photo.jpg' }); // '/home/mara/photo.jpg'
path.format({ dir: '/home/mara', name: 'photo', ext: '.jpg' });
// 4) Convenience getters
path.basename('/foo/bar/baz.txt'); // 'baz.txt'
path.basename('/foo/bar/baz.txt', '.txt'); // 'baz'
path.dirname('/foo/bar/baz.txt'); // '/foo/bar'
path.extname('/foo/bar/baz.txt'); // '.txt'
path.extname('baz'); // ''
// 5) Relative path between two paths
path.relative('/home/mara/project', '/home/mara/photos'); // '../photos'
path.relative('/srv/app', '/srv/app/src/index.ts'); // 'src/index.ts'
// 6) Normalise — clean up '..' and double slashes
path.normalize('/foo//bar/../baz/'); // '/foo/baz/'
path.normalize('a/b/../c/./d'); // 'a/c/d'
// 7) Platform info
path.sep // '/' on POSIX, '\\\\' on Windows
path.delimiter // ':' on POSIX, ';' on Windows (used in PATH env var)
// Cross-platform tests
path.posix.join('a', 'b'); // 'a/b' regardless of host
path.win32.join('a', 'b'); // 'a\\b' regardless of host
// 8) Working with ESM module URLs (no __dirname in ESM)
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const staticDir = path.join(__dirname, 'public');
// 9) Common patterns
// Reading a file beside the script
import fs from 'node:fs/promises';
const config = await fs.readFile(path.join(__dirname, 'config.json'), 'utf8');
// Building project-relative paths
const ROOT = path.resolve(__dirname, '..'); // go up from /src to /
const SRC = path.join(ROOT, 'src');
const BUILD = path.join(ROOT, 'build');
// Safe upload destination
function safeUploadPath(filename) {
const base = path.basename(filename); // strips ../ traversal
if (base !== filename || base.startsWith('.')) throw new Error('invalid name');
return path.join('/srv/uploads', base);
}
// 10) Globbing
// path is JUST for paths; for glob matching use the picomatch / fast-glob libs
// import { glob } from 'fast-glob';
// const files = await glob('src/**/*.ts');
// 11) Common bugs
// • Concatenating with '/' — breaks on Windows; always path.join / resolve
// • Trusting user input as a path — directory traversal; sanitise with basename + allowlist
// • Forgetting __dirname is undefined in ESM — recreate via fileURLToPath
// • path.resolve from a non-cwd context — call path.resolve(import.meta.url) won't work
// • Mixing path.posix / path.win32 with default path — keep one
// • Tilde (~) expansion — Node doesn't expand it; pass path.resolve(os.homedir(), '.config')
// • Relative vs absolute confusion — when in doubt, log path.isAbsolute(p) before fs operations
Why it matters
Always go through node:path for filesystem paths — join for relatives, resolve for absolutes, basename to scrub uploaded filenames, and fileURLToPath(import.meta.url) to recover __dirname under ESM. Concatenating slashes manually breaks on Windows; the path module makes it stop.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
import path from 'node:path';
path.join(__dirname, 'data', 'users.json');
path.basename('/a/b.txt'); // 'b.txt'
Try it Yourself »
Discussion
Loading…