Core 02 - Path Module
Table of Contents
- Basic Idea
- path.join() vs path.resolve()
- path.basename(), path.dirname(), path.extname()
- path.parse() and path.format()
- path.normalize() and path.isAbsolute()
- path.sep and path.delimiter
- Security: path.join + __dirname Is Not Security
- Summary
- Prerequisites
Basic Idea
File paths look different on every OS
Windows uses backslashes , Linux uses forward slashes , and nobody can agree on anything
path module abstracts all that garbage so your code works everywhere
path.join() vs path.resolve()
This is the most common confusion Both combine path segments but they behave differently
const path = require('path')
// join - concatenates with platform separator , no magic
const joined = path.join('/var', 'data', 'config.json')
// Linux: /var/data/config.json
// Windows: \var\data\config.json
// resolve - treats arguments as cd commands , returns absolute
const resolved = path.resolve('..', 'config', 'app.json')
// /home/user/project/../config/app.json -> /home/user/project/config/app.json
// resolve with absolute segment resets
const test = path.resolve('/etc', '/tmp', 'file.txt')
// /tmp/file.txt - /tmp resets the path
join is safe concatenation
resolve is like mentally typing cd through each argument - if one starts with / , everything before it gets thrown away
// practical difference
const cfg1 = path.join('/app', '../../etc/passwd') // /app/../../etc/passwd (literal)
const cfg2 = path.resolve('/app', '../../etc/passwd') // /etc/passwd (resolved)
path.basename(), path.dirname(), path.extname()
const filepath = '/var/log/nginx/access.log'
// filenmae with extension
console.log(path.basename(filepath)) // access.log
// filenmae without extension
console.log(path.basename(filepath, '.log')) // access
// directory containing the file
console.log(path.dirname(filepath)) // /var/log/nginx
// extension only
console.log(path.extname(filepath)) // .log
These are pure string operations - they don’t check if the file actually exists
Use fs.stat() if you need existence verification
path.parse() and path.format()
const parsed = path.parse('/home/user/docs/report.pdf')
console.log(parsed)
// {
// root: '/',
// dir: '/home/user/docs',
// base: 'report.pdf',
// name: 'report',
// ext: '.pdf'
// }
// reverse - build a path from its components
const reconstructed = path.format({
root: '/',
dir: '/home/user/docs',
name: 'report',
ext: '.pdf'
})
// /home/user/docs/report.pdf
parse is the swiss army knife for breaking down paths
format rebuilds them - useful when you manipulate components individually
path.normalize() and path.isAbsolute()
// normalize cleans up weird paths
const dirty = '/var//data/./config/../app.json'
const clean = path.normalize(dirty)
// /var/data/app.json
// is absolute check
console.log(path.isAbsolute('/etc/passwd')) // true
console.log(path.isAbsolute('./config.json')) // false
console.log(path.isAbsolute('config.json')) // false
normalize resolves . and .. segments without hitting the filesystem
Always normalize user-provided paths before security checks
path.sep and path.delimiter
// path separator - / on Linux, \ on Windows
const segments = '/usr/local/bin'.split(path.sep)
// ['', 'usr', 'local', 'bin']
// PATH delimiter - : on Linux, ; on Windows
const pathEntries = process.env.PATH.split(path.delimiter)
// ['/usr/bin', '/usr/local/bin', ...]
These are how you write cross-platform code
Never hardcode / or : in path operations - use path.sep and path.delimiter
Security: path.join + __dirname Is Not Security
// FALSE SECURITY - this is NOT safe
const path = require('path')
const safePath = path.join(__dirname, '../../etc/passwd')
// This resolves to the actual /etc/passwd file !!!
// REAL safest - normalize and check prefix
const baseDir = path.resolve(__dirname, '..', 'data')
function secureJoin(userInput) {
const target = path.normalize(path.join(baseDir, userInput))
if (!target.startsWith(baseDir)) {
throw new Error('path traversal detected')
}
return target
}
path.join with __dirname doesn’t prevent traversal - it enables it
The .. segments resolve upward regardless
Always check the resolved path is still within your intended directory
Another gotcha: path.normalize on Windows processes \\ as UNC paths
Windows users should also check the resolved drive letter matches expectations
// Windows edge case
const malicious = 'C:\\Windows\\System32\\drivers\\etc\\hosts'
const resolved = path.resolve('D:\\app', malicious)
// C:\Windows\System32\drivers\etc\hosts - different drive!
Summary
joinconcatenates ,resolvemakes absoluteparseandformatlet you manipulate path components safelynormalizebefore security checks ,isAbsolutefor absolute checks- Never trust
path.join(baseDir, userInput)alone - verify the result
Prerequisites
next -> core_03_os
Posted · Updated