Skip to content

Core 02 path

Core 02 - Path Module

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

  • join concatenates , resolve makes absolute
  • parse and format let you manipulate path components safely
  • normalize before security checks , isAbsolute for absolute checks
  • Never trust path.join(baseDir, userInput) alone - verify the result

Prerequisites

next -> core_03_os.md