path module
The path module is a core Node.js module that provides utilities for manipulating file paths. It solves the problem of cross-platform path compatibility—paths on Windows use backslashes (\\) while Unix-like systems use forward slashes (/). This module lets you write path-agnostic code that works everywhere.
Syntax
const path = require('path'); // CommonJS
import path from 'path'; // ESM
Methods
path.resolve([…paths])
Resolves a sequence of path segments into an absolute path.
| Parameter | Type | Default | Description |
|---|---|---|---|
...paths | string | — | Path segments to resolve |
Returns: string — An absolute path
path.join([…paths])
Joins path segments together using the platform-specific separator.
| Parameter | Type | Default | Description |
|---|---|---|---|
...paths | string | — | Path segments to join |
Returns: string — A joined path
path.basename(path[, ext])
Returns the last portion of a path.
| Parameter | Type | Default | Description |
|---|---|---|---|
path | string | — | The path to evaluate |
ext | string | '' | Optional file extension to remove |
Returns: string — The filename
path.dirname(path)
Returns the directory name of a path.
| Parameter | Type | Default | Description |
|---|---|---|---|
path | string | — | The path to evaluate |
Returns: string — The directory path
path.extname(path)
Returns the extension of the path.
| Parameter | Type | Default | Description |
|---|---|---|---|
path | string | — | The path to evaluate |
Returns: string — The extension including the dot
path.parse(path)
Returns an object with the path’s components.
| Parameter | Type | Default | Description |
|---|---|---|---|
path | string | — | The path to parse |
Returns: Object — { root, dir, base, ext, name }
path.format(pathObject)
Returns a path string from an object (opposite of parse).
| Parameter | Type | Default | Description |
|---|---|---|---|
pathObject | Object | — | Object with path components |
Returns: string — The formatted path
path.isAbsolute(path)
Determines if a path is absolute.
| Parameter | Type | Default | Description |
|---|---|---|---|
path | string | — | The path to check |
Returns: boolean — True if absolute
path.relative(from, to)
Returns the relative path from one path to another.
| Parameter | Type | Default | Description |
|---|---|---|---|
from | string | — | Source path |
to | string | — | Target path |
Returns: string — The relative path
path.normalize(path)
Normalizes a path, resolving .. and . segments.
| Parameter | Type | Default | Description |
|---|---|---|---|
path | string | — | The path to normalize |
Returns: string — The normalized path
Examples
Basic path manipulation
const path = require('path');
console.log(__filename);
// /Users/ludwig/project/src/app.js
console.log(__dirname);
// /Users/ludwig/project/src
// Get the filename
console.log(path.basename(__filename));
// app.js
// Get the extension
console.log(path.extname(__filename));
// .js
// Get the directory
console.log(path.dirname(__filename));
// /Users/ludwig/project/src
The example above extracts parts of a path one method at a time — getting the basename, extension, and directory independently. That is useful for inspection, but you often need to go the other direction: building paths from pieces. The next example shows path.join() and path.resolve() putting segments together in a platform-safe way, so your code does not break when it moves from macOS to Windows.
Building cross-platform paths
const path = require('path');
// Join path segments - automatically uses correct separator
const fullPath = path.join('src', 'components', 'Button.js');
console.log(fullPath);
// 'src/components/Button.js' on Unix
// 'src\\components\\Button.js' on Windows
// Resolve to absolute path
const absolutePath = path.resolve('src', 'app.js');
console.log(absolutePath);
// '/Users/ludwig/project/src/app.js'
path.join() and path.resolve() both assemble paths from segments, but resolve() always produces an absolute path by prepending the current working directory. Now look at the flip side: once you have a full path, you may want to take it apart into named pieces. path.parse() does exactly that, returning an object with root, dir, base, ext, and name fields.
Parsing file paths
const path = require('path');
const parsed = path.parse('/home/user/docs/report.pdf');
console.log(parsed);
// {
// root: '/',
// dir: '/home/user/docs',
// base: 'report.pdf',
// ext: '.pdf',
// name: 'report'
// }
// Reconstruct from parsed object
console.log(path.format(parsed));
// '/home/user/docs/report.pdf'
path.parse() and path.format() are inverses: parse breaks a path into an object, and format reassembles that object back into a string. You do not always need the full breakdown, though. When you only care about the extension — for checking file types or swapping .txt for .html — path.extname() and path.basename() with the optional second argument are the right tools.
Working with file extensions
const path = require('path');
// Change extension
const file = 'document.txt';
const html = path.basename(file, path.extname(file)) + '.html';
console.log(html);
// 'document.html'
// Check if file has specific extension
function hasExtension(filePath, ext) {
return path.extname(filePath) === ext;
}
console.log(hasExtension('image.png', '.png'));
// true
console.log(hasExtension('image.jpg', '.png'));
// false
Checking and changing extensions handles one file at a time. When your code needs to describe how to get from one directory to another — say, for import statements or build tool output — path.relative() answers the question directly. It computes the shortest relative path between two absolute paths without touching the filesystem.
Path relative calculations
const path = require('path');
// Get relative path between two directories
const from = '/home/user/project/src';
const to = '/home/user/project/dist/bundle.js';
console.log(path.relative(from, to));
// '../dist/bundle.js'
// Useful for generating import statements
const sourceFile = '/home/user/project/src/utils.js';
const targetDir = '/home/user/project/src/components';
console.log(path.relative(targetDir, sourceFile));
// '../utils.js'
The examples so far show individual methods in isolation. Real code combines them to solve recurring problems. The patterns below cover three common scenarios: locating project files relative to the current module, safely joining user-supplied path input, and handling platform-specific executable extensions.
Common Patterns
Resolving project paths
const path = require('path');
// Assuming this file is in src/utils/
const projectRoot = path.resolve(__dirname, '..');
const configPath = path.join(projectRoot, 'config.json');
const srcPath = path.join(projectRoot, 'src');
Locating files relative to __dirname works when you control the input. User-supplied paths need more care. A naive path.join() trusts whatever the caller provides, which opens the door to path traversal attacks. The next pattern uses path.resolve() to normalize the result and then checks that it stays inside the allowed directory — a simple but effective guard.
Safe path joining with user input
const path = require('path');
function safeJoin(base, userInput) {
// Resolve the target path
const targetPath = path.resolve(base, userInput);
// Ensure it's still within the base directory
if (!targetPath.startsWith(base)) {
throw new Error('Path traversal detected');
}
return targetPath;
}
// Usage
const baseDir = '/home/user/uploads';
console.log(safeJoin(baseDir, 'avatar.png'));
// '/home/user/uploads/avatar.png'
console.log(safeJoin(baseDir, '../etc/passwd'));
// Error: Path traversal detected
Security checks like startsWith keep path operations inside a trusted root, but there is one more portability detail worth handling: executable extensions. On Windows, binaries end in .exe. On macOS and Linux they do not. The pattern below detects the platform and appends the right suffix, so your path-building logic works identically on every OS.
Cross-platform executable paths
const path = require('path');
// Detect OS and use appropriate extension
const execExt = process.platform === 'win32' ? '.exe' : '';
const binPath = path.join(__dirname, 'bin', 'myapp' + execExt);
When to Reach for path
The path module is most helpful any time your code needs to reason about file names instead of file contents. It can assemble paths, split them apart, normalize redundant segments, and translate between relative and absolute forms. That makes it a natural fit for build tools, file upload handlers, CLI utilities, and server code that needs to locate assets.
Its main value is portability. Path separators differ across operating systems, and path rules around roots, drive letters, and relative segments can get messy quickly. Using the module keeps those details in one place so the rest of your code can stay focused on the task at hand.
Safety and Normalization
path.normalize() and path.resolve() are especially useful when input may contain . or .. segments. They help turn a messy path string into one that is easier to compare or store. That does not automatically make a path safe, though. If you accept user input, you still need to verify that the resolved result stays inside the directory you expect.
That check is important because path manipulation and security are closely related. A path can be syntactically valid but still point somewhere dangerous. Treat path as a parsing and formatting tool, then layer your own policy checks on top.