π¦ The path Module
path is a built-in Node.js module for working with file and folder paths β joining them, pulling them apart, and making sure they work correctly across Windows, Mac, and Linux (which use different slashes).
const path = require('path');π Joining Paths
const fullPath = path.join('myFolder', 'sub', 'info.txt');
console.log(fullPath);Output:
myFolder/sub/info.txtpath.join(...)glues path segments together into one clean path.It automatically uses the right slash for the OS (
/on Mac/Linux,\on Windows) β no manual string concatenation needed.It also cleans up extra slashes, so
path.join('a/', '/b')still gives youa/b, nota//b.
project/
βββ myFolder/
βββ sub/
βββ info.txt β the path this points toπ Resolving Absolute Paths
const absolute = path.resolve('myFolder', 'info.txt');
console.log(absolute);Output (if run from /Users/bhaskar/project):
/Users/bhaskar/project/myFolder/info.txtpath.resolve(...)builds a full, absolute path, starting from your current working directory.Unlike
join, which just glues segments together as-is,resolvefigures out where that path actually lives on disk.Useful when a script needs to know the real location of a file, no matter where it's run from.
π·οΈ Getting Parts of a Path
const filePath = '/project/myFolder/info.txt';
console.log(path.basename(filePath)); // 'info.txt'
console.log(path.dirname(filePath)); // '/project/myFolder'
console.log(path.extname(filePath)); // '.txt'basename(path)returns just the file name at the end of the path.dirname(path)returns everything except the file name β the folder it lives in.extname(path)returns the file extension, including the dot.
/project/myFolder/info.txt
ββββ¬ββββββ¬ββ
dirname basename
ββ¬β
extnameπ§© Breaking a Path Fully Apart
const parsed = path.parse('/project/myFolder/info.txt');
console.log(parsed);Output:
{
root: '/',
dir: '/project/myFolder',
base: 'info.txt',
ext: '.txt',
name: 'info'
}path.parse(path)splits a path into all its pieces at once β root, directory, full file name, extension, and name without the extension.Handy when you need more than one part of the path and don't want to call
basename,dirname, andextnameseparately.
π Reversing It: Building a Path from Parts
const built = path.format({
dir: '/project/myFolder',
base: 'info.txt'
});
console.log(built); // '/project/myFolder/info.txt'path.format(obj)does the opposite ofpath.parseβ turns an object of path pieces back into a path string.
β Checking & Cleaning Paths
console.log(path.isAbsolute('/project/info.txt')); // true
console.log(path.isAbsolute('info.txt')); // false
console.log(path.normalize('myFolder//sub/../info.txt')); // 'myFolder/info.txt'isAbsolute(path)tells you whether a path starts from the root (true) or is relative to somewhere else (false).normalize(path)cleans up messy paths β collapsing double slashes and resolving..and.segments β without needing the file to actually exist.
π‘ Why Use path Instead of Plain Strings?
Writing
'myFolder' + '/' + 'info.txt'breaks on Windows, where paths use\instead of/.pathmethods handle that difference automatically, so the same code works on every OS.Combine it with
fsto build reliable, cross-platform file operations:
const fs = require('fs');
const path = require('path');
const filePath = path.join(__dirname, 'myFolder', 'info.txt');
fs.writeFileSync(filePath, 'Hello!');__dirnameis a built-in variable holding the folder your current script lives in β combining it withpath.joinguarantees the file goes in the right place, regardless of where the script is run from.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>path, live</title>
</head>
<body>
<div class="path-info" id="pathinfo">
<style>
.path-info {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft: #e3ebfd;
--line: #ddd6c9;
--display-font: 'Fraunces', Georgia, serif;
--body-font: 'Inter', system-ui, sans-serif;
--mono-font: 'IBM Plex Mono', monospace;
max-width: 880px;
margin: 0 auto;
box-sizing: border-box;
padding: 2rem;
font-family: var(--body-font);
color: var(--ink);
background: var(--paper);
}
.path-info[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1c212b;
--accent: #5b8def;
--accent-soft: #202942;
--line: #2c313d;
}
.path-info * { box-sizing: border-box; }
.pt-eyebrow {
font-family: var(--mono-font); font-size: 0.68rem; letter-spacing: 0.14em; text-transform: uppercase;
color: var(--accent); text-align: center; margin: 1.5rem 0 0.3rem;
}
.pt-title {
font-family: var(--display-font); font-size: clamp(1.5rem, 5vw, 2rem); text-align: center;
margin: 0 0 0.5rem; letter-spacing: -0.01em;
}
.pt-sub { color: var(--ink-soft); font-size: 0.88rem; text-align: center; margin: 0 0 1.4rem; max-width: 480px; margin-left: auto; margin-right: auto; }
/* ---- tab bar ---- */
.pt-tabbar { display: flex; gap: 0.3rem; overflow-x: auto; margin-bottom: 1.3rem; border-bottom: 1px solid var(--line); padding-bottom: 0; }
.pt-tab {
flex: 0 0 auto; font-family: var(--mono-font); font-size: 0.74rem; font-weight: 600; padding: 0.55rem 0.8rem;
border: none; background: none; color: var(--ink-soft); cursor: pointer; white-space: nowrap;
border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.pt-tab.is-active { color: var(--accent); border-bottom-color: var(--accent); }
/* ---- panel ---- */
.pt-panel { display: none; }
.pt-panel.is-active { display: block; }
.pt-desc { font-size: 0.85rem; color: var(--ink-soft); margin-bottom: 1.1rem; line-height: 1.5; }
.pt-desc b { color: var(--ink); }
.pt-desc code { background: var(--paper-alt); padding: 0.05rem 0.35rem; border-radius: 3px; font-family: var(--mono-font); font-size: 0.85em; }
.pt-field { margin-bottom: 0.7rem; }
.pt-field-label { font-family: var(--mono-font); font-size: 0.68rem; font-weight: 700; color: var(--ink-soft); margin-bottom: 0.3rem; display: block; }
.pt-input {
width: 100%; font-family: var(--mono-font); font-size: 0.82rem; padding: 0.5rem 0.7rem;
border: 1px solid var(--line); border-radius: 6px; background: var(--paper-alt); color: var(--ink);
}
.pt-input:focus { outline: none; border-color: var(--accent); }
.pt-row { display: flex; gap: 0.6rem; }
.pt-row .pt-field { flex: 1; }
.pt-arrow { text-align: center; font-size: 1.1rem; color: var(--ink-soft); margin: 0.5rem 0; }
.pt-run-row { display: flex; justify-content: center; margin: 0.8rem 0; }
.pt-run-btn {
font-family: var(--mono-font); font-size: 0.8rem; font-weight: 600; padding: 0.5rem 1rem;
border-radius: 5px; border: 1px solid var(--accent); background: var(--accent); color: #fff; cursor: pointer;
}
.pt-output.is-empty { color: var(--ink-soft); font-style: italic; }
.pt-output {
background: #1e1e1e; border-radius: 8px; padding: 0.85rem 1rem; font-family: var(--mono-font);
font-size: 0.82rem; line-height: 1.7; color: #d4d4d8; margin-bottom: 0.8rem; white-space: pre-wrap; word-break: break-all;
}
.pt-output .k { color: #61afef; }
.pt-output .v { color: #98c379; font-weight: 700; }
.pt-output .b { color: #e5c07b; font-weight: 700; }
.pt-toggle { display: flex; justify-content: center; gap: 0.4rem; margin-bottom: 1rem; }
.pt-toggle-btn {
font-family: var(--body-font); font-size: 0.78rem; font-weight: 600; padding: 0.4rem 0.75rem;
border-radius: 16px; border: 1px solid var(--line); background: var(--paper); color: var(--ink-soft); cursor: pointer;
}
.pt-toggle-btn.is-selected { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); }
.pt-code-static {
background: #1e1e1e; border-radius: 8px; padding: 0.85rem 1rem; font-family: var(--mono-font);
font-size: 0.76rem; line-height: 1.7; color: #d4d4d8; margin: 0.9rem 0 0.6rem; white-space: pre;
}
.pt-code-static .k { color: #c678dd; }
.pt-code-static .f { color: #61afef; }
.pt-code-static .s { color: #98c379; }
.pt-code-static .c { color: #6a9955; font-style: italic; }
.pt-hint { text-align: center; font-size: 0.76rem; color: var(--ink-soft); margin: 0 0 2rem; }
</style>
<h1 class="pt-title">Full Working Example</h1>
<div class="pt-tabbar" id="ptTabbar">
<button type="button" class="pt-tab is-active" data-tab="join">join</button>
<button type="button" class="pt-tab" data-tab="resolve">resolve</button>
<button type="button" class="pt-tab" data-tab="parts">basename/dirname/ext</button>
<button type="button" class="pt-tab" data-tab="parse">parse</button>
<button type="button" class="pt-tab" data-tab="format">format</button>
<button type="button" class="pt-tab" data-tab="check">isAbsolute/normalize</button>
<button type="button" class="pt-tab" data-tab="why">why path?</button>
</div>
<div class="pt-panel is-active" data-panel="join">
<p class="pt-desc"><code>path.join(...)</code> glues segments into one clean path — right slashes, no double-ups.</p>
<div class="pt-row">
<div class="pt-field"><span class="pt-field-label">SEGMENT 1</span><input class="pt-input" id="joinA" value="myFolder"></div>
<div class="pt-field"><span class="pt-field-label">SEGMENT 2</span><input class="pt-input" id="joinB" value="sub"></div>
<div class="pt-field"><span class="pt-field-label">SEGMENT 3</span><input class="pt-input" id="joinC" value="info.txt"></div>
</div>
<div class="pt-run-row"><button type="button" class="pt-run-btn" id="joinRunBtn">▶ Run path.join()</button></div>
<div class="pt-output is-empty" id="joinOut">— press Run to see the result —</div>
</div>
<div class="pt-panel" data-panel="resolve">
<p class="pt-desc"><code>path.resolve(...)</code> builds a full absolute path, starting from a current directory.</p>
<div class="pt-field"><span class="pt-field-label">CURRENT DIRECTORY (SIMULATED)</span><input class="pt-input" id="resolveCwd" value="/Users/bhaskar/project"></div>
<div class="pt-row">
<div class="pt-field"><span class="pt-field-label">SEGMENT 1</span><input class="pt-input" id="resolveA" value="myFolder"></div>
<div class="pt-field"><span class="pt-field-label">SEGMENT 2</span><input class="pt-input" id="resolveB" value="info.txt"></div>
</div>
<div class="pt-run-row"><button type="button" class="pt-run-btn" id="resolveRunBtn">▶ Run path.resolve()</button></div>
<div class="pt-output is-empty" id="resolveOut">— press Run to see the result —</div>
</div>
<div class="pt-panel" data-panel="parts">
<p class="pt-desc"><code>basename</code>, <code>dirname</code> and <code>extname</code> each pull out one piece of a path.</p>
<div class="pt-field"><span class="pt-field-label">PATH</span><input class="pt-input" id="partsPath" value="/project/myFolder/info.txt"></div>
<div class="pt-run-row"><button type="button" class="pt-run-btn" id="partsRunBtn">▶ Run</button></div>
<div class="pt-output is-empty" id="partsOut">— press Run to see the result —</div>
</div>
<div class="pt-panel" data-panel="parse">
<p class="pt-desc"><code>path.parse(...)</code> splits a path into all its pieces at once.</p>
<div class="pt-field"><span class="pt-field-label">PATH</span><input class="pt-input" id="parsePath" value="/project/myFolder/info.txt"></div>
<div class="pt-run-row"><button type="button" class="pt-run-btn" id="parseRunBtn">▶ Run path.parse()</button></div>
<div class="pt-output is-empty" id="parseOut">— press Run to see the result —</div>
</div>
<div class="pt-panel" data-panel="format">
<p class="pt-desc"><code>path.format(obj)</code> does the opposite of <code>parse</code> — pieces back into a path.</p>
<div class="pt-row">
<div class="pt-field"><span class="pt-field-label">DIR</span><input class="pt-input" id="formatDir" value="/project/myFolder"></div>
<div class="pt-field"><span class="pt-field-label">BASE</span><input class="pt-input" id="formatBase" value="info.txt"></div>
</div>
<div class="pt-run-row"><button type="button" class="pt-run-btn" id="formatRunBtn">▶ Run path.format()</button></div>
<div class="pt-output is-empty" id="formatOut">— press Run to see the result —</div>
</div>
<div class="pt-panel" data-panel="check">
<p class="pt-desc"><code>isAbsolute</code> checks if a path starts from root. <code>normalize</code> cleans up a messy one.</p>
<div class="pt-field"><span class="pt-field-label">PATH TO CHECK</span><input class="pt-input" id="absPath" value="/project/info.txt"></div>
<div class="pt-field" style="margin-top: 0.9rem;"><span class="pt-field-label">MESSY PATH TO CLEAN</span><input class="pt-input" id="normPath" value="myFolder//sub/../info.txt"></div>
<div class="pt-run-row"><button type="button" class="pt-run-btn" id="checkRunBtn">▶ Run</button></div>
<div class="pt-output is-empty" id="absOut">— press Run to see the result —</div>
<div class="pt-output is-empty" id="normOut">— press Run to see the result —</div>
</div>
<div class="pt-panel" data-panel="why">
<p class="pt-desc">Same two pieces, two ways of combining them. Try a folder that already ends in a slash.</p>
<div class="pt-row">
<div class="pt-field"><span class="pt-field-label">FOLDER</span><input class="pt-input" id="whyFolder" value="myFolder/"></div>
<div class="pt-field"><span class="pt-field-label">FILE</span><input class="pt-input" id="whyFile" value="info.txt"></div>
</div>
<div class="pt-run-row"><button type="button" class="pt-run-btn" id="whyRunBtn">▶ Run</button></div>
<div class="pt-output is-empty" id="whyOut">— press Run to see the result —</div>
<div class="pt-code-static"><span class="k">const</span> fs = <span class="f">require</span><span class="k">(</span><span class="s">'fs'</span><span class="k">)</span>;
<span class="k">const</span> path = <span class="f">require</span><span class="k">(</span><span class="s">'path'</span><span class="k">)</span>;
<span class="c">// always correct, no matter where this script runs from</span>
<span class="k">const</span> filePath = path.<span class="f">join</span><span class="k">(</span>__dirname, <span class="s">'myFolder'</span>, <span class="s">'info.txt'</span><span class="k">)</span>;
fs.<span class="f">writeFileSync</span><span class="k">(</span>filePath, <span class="s">'Hello!'</span><span class="k">)</span>;</div>
</div>
<p class="pt-hint">All output shown here uses POSIX-style forward slashes, same as the examples above.</p>
</div>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Fraunces:wght@500;600&family=Inter:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet">
<script>
(function () {
var root = document.getElementById('pathinfo');
if (!root) return;
var bodyAttr = document.body.getAttribute('data-darkmode');
var dark;
if (bodyAttr === 'true') dark = true;
else if (bodyAttr === 'false') dark = false;
else {
var match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) root.setAttribute('data-theme', 'dark');
/* ---- verified POSIX path implementation ---- */
function pNormalize(p) {
if (p === '') return '.';
var isAbsolute = p.charAt(0) === '/';
var trailingSlash = p.length > 1 && p.charAt(p.length - 1) === '/';
var segments = p.split('/').filter(function (s) { return s !== '' && s !== '.'; });
var out = [];
for (var i = 0; i < segments.length; i++) {
var seg = segments[i];
if (seg === '..') {
if (out.length && out[out.length - 1] !== '..') out.pop();
else if (!isAbsolute) out.push('..');
} else {
out.push(seg);
}
}
var result = out.join('/');
if (isAbsolute) result = '/' + result;
if (trailingSlash && result.charAt(result.length - 1) !== '/') result += '/';
if (result === '') result = isAbsolute ? '/' : '.';
return result;
}
function pJoin() {
var parts = Array.prototype.slice.call(arguments);
var joined = parts.filter(function (p) { return p !== undefined && p !== null && p !== ''; }).join('/');
if (joined === '') return '.';
return pNormalize(joined);
}
function pIsAbsolute(p) { return p.charAt(0) === '/'; }
function pResolve() {
var all = Array.prototype.slice.call(arguments);
var resolved = '';
var resolvedAbsolute = false;
for (var i = all.length - 1; i >= 0 && !resolvedAbsolute; i--) {
var seg = all[i];
if (!seg) continue;
resolved = seg + (resolved ? '/' + resolved : '');
resolvedAbsolute = pIsAbsolute(seg);
}
if (!resolvedAbsolute) resolved = '/' + resolved;
return pNormalize(resolved);
}
function pBasename(p, ext) {
var base = p.split('/').filter(Boolean).pop() || '';
if (ext && base.slice(-ext.length) === ext && base !== ext) base = base.slice(0, -ext.length);
return base;
}
function pDirname(p) {
if (p === '') return '.';
var isAbs = p.charAt(0) === '/';
var segs = p.split('/');
segs.pop();
var dir = segs.filter(Boolean).join('/');
if (isAbs) return '/' + dir;
return dir === '' ? '.' : dir;
}
function pExtname(p) {
var base = pBasename(p);
var idx = base.lastIndexOf('.');
if (idx <= 0) return '';
return base.slice(idx);
}
function pParse(p) {
var root = pIsAbsolute(p) ? '/' : '';
var dir = pDirname(p);
var base = pBasename(p);
var ext = pExtname(p);
var name = ext ? base.slice(0, -ext.length) : base;
return { root: root, dir: dir, base: base, ext: ext, name: name };
}
function pFormat(obj) {
var dir = obj.dir || obj.root || '';
var base = obj.base || ((obj.name || '') + (obj.ext || ''));
if (!dir) return base;
return dir === obj.root ? dir + base : dir + '/' + base;
}
/* ---- tabs ---- */
var tabs = root.querySelectorAll('.pt-tab');
var panels = root.querySelectorAll('.pt-panel');
tabs.forEach(function (tab) {
tab.addEventListener('click', function () {
var name = tab.getAttribute('data-tab');
tabs.forEach(function (t) { t.classList.toggle('is-active', t === tab); });
panels.forEach(function (p) { p.classList.toggle('is-active', p.getAttribute('data-panel') === name); });
});
});
function esc(s) { return String(s).replace(/&/g, '&').replace(/</g, '<'); }
/* ---- join ---- */
var joinA = root.querySelector('#joinA'), joinB = root.querySelector('#joinB'), joinC = root.querySelector('#joinC');
var joinOut = root.querySelector('#joinOut');
function updateJoin() {
var r = pJoin(joinA.value, joinB.value, joinC.value);
joinOut.classList.remove('is-empty');
joinOut.innerHTML = '<span class="k">path.join()</span> = <span class="v">\'' + esc(r) + '\'</span>';
}
root.querySelector('#joinRunBtn').addEventListener('click', updateJoin);
/* ---- resolve ---- */
var resolveCwd = root.querySelector('#resolveCwd'), resolveA = root.querySelector('#resolveA'), resolveB = root.querySelector('#resolveB');
var resolveOut = root.querySelector('#resolveOut');
function updateResolve() {
var r = pResolve(resolveCwd.value, resolveA.value, resolveB.value);
resolveOut.classList.remove('is-empty');
resolveOut.innerHTML = '<span class="k">path.resolve()</span> = <span class="v">\'' + esc(r) + '\'</span>';
}
root.querySelector('#resolveRunBtn').addEventListener('click', updateResolve);
/* ---- parts ---- */
var partsPath = root.querySelector('#partsPath'), partsOut = root.querySelector('#partsOut');
function updateParts() {
var p = partsPath.value;
partsOut.classList.remove('is-empty');
partsOut.innerHTML =
'<span class="k">basename</span> = <span class="v">\'' + esc(pBasename(p)) + '\'</span>\n' +
'<span class="k">dirname </span> = <span class="v">\'' + esc(pDirname(p)) + '\'</span>\n' +
'<span class="k">extname </span> = <span class="v">\'' + esc(pExtname(p)) + '\'</span>';
}
root.querySelector('#partsRunBtn').addEventListener('click', updateParts);
/* ---- parse ---- */
var parsePath = root.querySelector('#parsePath'), parseOut = root.querySelector('#parseOut');
function updateParse() {
var o = pParse(parsePath.value);
parseOut.classList.remove('is-empty');
parseOut.innerHTML =
'{\n' +
' <span class="k">root</span>: <span class="v">\'' + esc(o.root) + '\'</span>,\n' +
' <span class="k">dir</span>: <span class="v">\'' + esc(o.dir) + '\'</span>,\n' +
' <span class="k">base</span>: <span class="v">\'' + esc(o.base) + '\'</span>,\n' +
' <span class="k">ext</span>: <span class="v">\'' + esc(o.ext) + '\'</span>,\n' +
' <span class="k">name</span>: <span class="v">\'' + esc(o.name) + '\'</span>\n' +
'}';
}
root.querySelector('#parseRunBtn').addEventListener('click', updateParse);
/* ---- format ---- */
var formatDir = root.querySelector('#formatDir'), formatBase = root.querySelector('#formatBase'), formatOut = root.querySelector('#formatOut');
function updateFormat() {
var r = pFormat({ dir: formatDir.value, base: formatBase.value });
formatOut.classList.remove('is-empty');
formatOut.innerHTML = '<span class="k">path.format()</span> = <span class="v">\'' + esc(r) + '\'</span>';
}
root.querySelector('#formatRunBtn').addEventListener('click', updateFormat);
/* ---- check ---- */
var absPath = root.querySelector('#absPath'), absOut = root.querySelector('#absOut');
var normPath = root.querySelector('#normPath'), normOut = root.querySelector('#normOut');
function updateCheck() {
var r1 = pIsAbsolute(absPath.value);
absOut.classList.remove('is-empty');
absOut.innerHTML = '<span class="k">path.isAbsolute()</span> = <span class="b">' + r1 + '</span>';
var r2 = pNormalize(normPath.value);
normOut.classList.remove('is-empty');
normOut.innerHTML = '<span class="k">path.normalize()</span> = <span class="v">\'' + esc(r2) + '\'</span>';
}
root.querySelector('#checkRunBtn').addEventListener('click', updateCheck);
/* ---- why ---- */
var whyFolder = root.querySelector('#whyFolder'), whyFile = root.querySelector('#whyFile'), whyOut = root.querySelector('#whyOut');
function updateWhy() {
var f = whyFolder.value, file = whyFile.value;
var concat = f + '/' + file;
var joined = pJoin(f, file);
var broken = concat !== joined;
whyOut.classList.remove('is-empty');
whyOut.innerHTML =
'<span class="k">string concat</span> = <span class="v">\'' + esc(concat) + '\'</span>' + (broken ? ' <span style="color:#ff6b6b;">\u2190 double slash</span>' : '') + '\n' +
'<span class="k">path.join() </span> = <span class="v">\'' + esc(joined) + '\'</span>' + (broken ? ' <span style="color:#7ee787;">\u2190 clean</span>' : '');
}
root.querySelector('#whyRunBtn').addEventListener('click', updateWhy);
})();
</script>
</body>
</html>