The path module provides utilities for working with file and directory paths. Here's how to use it effectively.
Import#
1import path from 'node:path';
2
3// Or specific methods
4import { join, resolve, basename } from 'node:path';
5
6// Platform-specific
7import { posix, win32 } from 'node:path';path.join()#
1// Join path segments
2const fullPath = path.join('users', 'john', 'documents');
3// 'users/john/documents' (Unix)
4// 'users\\john\\documents' (Windows)
5
6// Handles extra slashes
7path.join('/foo/', '/bar/', 'baz/');
8// '/foo/bar/baz'
9
10// Resolves . and ..
11path.join('foo', 'bar', '..', 'baz');
12// 'foo/baz'
13
14// Practical usage
15const configPath = path.join(process.cwd(), 'config', 'app.json');
16const dataPath = path.join(__dirname, '..', 'data', 'users.json');path.resolve()#
1// Resolve to absolute path
2const absolute = path.resolve('foo', 'bar');
3// '/current/working/directory/foo/bar'
4
5// With leading slash - starts from root
6path.resolve('/foo', 'bar');
7// '/foo/bar'
8
9// Rightmost absolute path wins
10path.resolve('foo', '/bar', 'baz');
11// '/bar/baz'
12
13// Common patterns
14const projectRoot = path.resolve(__dirname, '..');
15const configFile = path.resolve(projectRoot, 'config.json');
16
17// From current directory
18const uploadDir = path.resolve('./uploads');path.basename()#
1// Get filename from path
2path.basename('/foo/bar/file.txt');
3// 'file.txt'
4
5// Remove extension
6path.basename('/foo/bar/file.txt', '.txt');
7// 'file'
8
9// Works with any path
10path.basename('C:\\Users\\file.txt');
11// 'file.txt' (on Unix, would be 'C:\\Users\\file.txt')
12
13// Get just the filename
14const files = ['/path/to/a.js', '/path/to/b.js'];
15const names = files.map(f => path.basename(f, '.js'));
16// ['a', 'b']path.dirname()#
1// Get directory from path
2path.dirname('/foo/bar/file.txt');
3// '/foo/bar'
4
5path.dirname('/foo/bar/');
6// '/foo'
7
8path.dirname('file.txt');
9// '.'
10
11// Navigate up directories
12const parentDir = path.dirname(__dirname);
13const grandparent = path.dirname(path.dirname(__dirname));path.extname()#
1// Get file extension
2path.extname('file.txt');
3// '.txt'
4
5path.extname('file.config.js');
6// '.js'
7
8path.extname('file');
9// ''
10
11path.extname('.gitignore');
12// ''
13
14path.extname('file.');
15// '.'
16
17// Filter by extension
18const files = ['app.js', 'style.css', 'data.json', 'readme.md'];
19const jsFiles = files.filter(f => path.extname(f) === '.js');
20// ['app.js']path.parse()#
1// Parse path into components
2const parsed = path.parse('/home/user/file.txt');
3// {
4// root: '/',
5// dir: '/home/user',
6// base: 'file.txt',
7// ext: '.txt',
8// name: 'file'
9// }
10
11// Windows path
12path.win32.parse('C:\\Users\\file.txt');
13// {
14// root: 'C:\\',
15// dir: 'C:\\Users',
16// base: 'file.txt',
17// ext: '.txt',
18// name: 'file'
19// }
20
21// Use parsed components
22const { name, ext } = path.parse(filename);
23const newName = `${name}.backup${ext}`;path.format()#
1// Build path from components
2const pathString = path.format({
3 dir: '/home/user',
4 base: 'file.txt'
5});
6// '/home/user/file.txt'
7
8// With all components
9path.format({
10 root: '/',
11 dir: '/home/user',
12 name: 'file',
13 ext: '.txt'
14});
15// '/home/user/file.txt'
16
17// base overrides name + ext
18path.format({
19 name: 'ignored',
20 ext: '.ignored',
21 base: 'file.txt'
22});
23// 'file.txt'
24
25// Rename file
26function renameFile(filepath, newName) {
27 const parsed = path.parse(filepath);
28 return path.format({
29 dir: parsed.dir,
30 name: newName,
31 ext: parsed.ext
32 });
33}
34
35renameFile('/foo/old.txt', 'new');
36// '/foo/new.txt'path.relative()#
1// Get relative path between two paths
2path.relative('/data/files', '/data/files/test/file.txt');
3// 'test/file.txt'
4
5path.relative('/data/files', '/data/other/file.txt');
6// '../other/file.txt'
7
8path.relative('/a/b/c', '/a/b/c');
9// ''
10
11// Practical: create relative imports
12function getRelativeImport(from, to) {
13 let relative = path.relative(path.dirname(from), to);
14 if (!relative.startsWith('.')) {
15 relative = './' + relative;
16 }
17 return relative.replace(/\\/g, '/');
18}
19
20getRelativeImport('/src/components/Button.js', '/src/utils/helpers.js');
21// '../utils/helpers.js'path.isAbsolute()#
1// Check if path is absolute
2path.isAbsolute('/foo/bar'); // true
3path.isAbsolute('./foo/bar'); // false
4path.isAbsolute('foo/bar'); // false
5
6// Windows
7path.win32.isAbsolute('C:\\foo'); // true
8path.win32.isAbsolute('\\foo'); // true
9path.win32.isAbsolute('foo'); // false
10
11// Validate input
12function processFile(filepath) {
13 const absolutePath = path.isAbsolute(filepath)
14 ? filepath
15 : path.resolve(process.cwd(), filepath);
16
17 // Now work with absolute path
18 return absolutePath;
19}path.normalize()#
1// Normalize path (resolve . and ..)
2path.normalize('/foo/bar//baz/asdf/quux/..');
3// '/foo/bar/baz/asdf'
4
5path.normalize('C:\\temp\\\\foo\\bar\\..\\');
6// 'C:\\temp\\foo\\'
7
8// Clean up user input
9function cleanPath(inputPath) {
10 return path.normalize(inputPath);
11}
12
13// Note: doesn't check if path exists
14path.normalize('/nonexistent/../foo');
15// '/foo'path.sep and path.delimiter#
1// Path separator
2path.sep;
3// '/' on Unix, '\\' on Windows
4
5// Split path into segments
6'/foo/bar/baz'.split(path.sep);
7// ['', 'foo', 'bar', 'baz']
8
9// PATH delimiter
10path.delimiter;
11// ':' on Unix, ';' on Windows
12
13// Parse PATH environment variable
14const paths = process.env.PATH.split(path.delimiter);
15
16// Build PATH
17const newPath = ['/usr/local/bin', '/usr/bin'].join(path.delimiter);Cross-Platform Paths#
1// Always use path.join for cross-platform
2// BAD
3const bad = dir + '/' + file;
4
5// GOOD
6const good = path.join(dir, file);
7
8// Force specific platform
9path.posix.join('foo', 'bar'); // Always 'foo/bar'
10path.win32.join('foo', 'bar'); // Always 'foo\\bar'
11
12// Convert Windows path to URL
13function pathToFileUrl(filepath) {
14 const absolute = path.resolve(filepath);
15 return 'file://' + absolute.split(path.sep).join('/');
16}
17
18// Normalize slashes for URLs
19function normalizeForUrl(filepath) {
20 return filepath.replace(/\\/g, '/');
21}Common Patterns#
1// Get project root (ES modules)
2import { fileURLToPath } from 'node:url';
3
4const __filename = fileURLToPath(import.meta.url);
5const __dirname = path.dirname(__filename);
6const projectRoot = path.resolve(__dirname, '..');
7
8// Ensure extension
9function ensureExtension(filepath, ext) {
10 if (path.extname(filepath) !== ext) {
11 return filepath + ext;
12 }
13 return filepath;
14}
15
16// Change extension
17function changeExtension(filepath, newExt) {
18 const { dir, name } = path.parse(filepath);
19 return path.join(dir, name + newExt);
20}
21
22changeExtension('/foo/bar.txt', '.md');
23// '/foo/bar.md'
24
25// Safe path joining (prevent traversal)
26function safePath(baseDir, userPath) {
27 const resolved = path.resolve(baseDir, userPath);
28 if (!resolved.startsWith(baseDir)) {
29 throw new Error('Path traversal detected');
30 }
31 return resolved;
32}Best Practices#
Cross-Platform:
✓ Always use path.join()
✓ Use path.sep when needed
✓ Test on multiple OS
✓ Avoid hardcoded separators
Security:
✓ Validate user-provided paths
✓ Prevent path traversal
✓ Use path.resolve for absolute
✓ Sanitize file names
ES Modules:
✓ Use import.meta.url
✓ Convert to __dirname
✓ Use fileURLToPath
✓ Handle Windows paths
Avoid:
✗ String concatenation for paths
✗ Hardcoded '/' or '\\'
✗ Trusting user input
✗ Mixing path styles
Conclusion#
The path module is essential for cross-platform file path handling. Use path.join() for combining paths, path.resolve() for absolute paths, and path.parse()/path.format() for manipulation. Always use path module methods instead of string concatenation to ensure cross-platform compatibility.