jsguides

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.

ParameterTypeDefaultDescription
...pathsstringPath segments to resolve

Returns: string — An absolute path

path.join([…paths])

Joins path segments together using the platform-specific separator.

ParameterTypeDefaultDescription
...pathsstringPath segments to join

Returns: string — A joined path

path.basename(path[, ext])

Returns the last portion of a path.

ParameterTypeDefaultDescription
pathstringThe path to evaluate
extstring''Optional file extension to remove

Returns: string — The filename

path.dirname(path)

Returns the directory name of a path.

ParameterTypeDefaultDescription
pathstringThe path to evaluate

Returns: string — The directory path

path.extname(path)

Returns the extension of the path.

ParameterTypeDefaultDescription
pathstringThe path to evaluate

Returns: string — The extension including the dot

path.parse(path)

Returns an object with the path’s components.

ParameterTypeDefaultDescription
pathstringThe path to parse

Returns: Object{ root, dir, base, ext, name }

path.format(pathObject)

Returns a path string from an object (opposite of parse).

ParameterTypeDefaultDescription
pathObjectObjectObject with path components

Returns: string — The formatted path

path.isAbsolute(path)

Determines if a path is absolute.

ParameterTypeDefaultDescription
pathstringThe path to check

Returns: boolean — True if absolute

path.relative(from, to)

Returns the relative path from one path to another.

ParameterTypeDefaultDescription
fromstringSource path
tostringTarget path

Returns: string — The relative path

path.normalize(path)

Normalizes a path, resolving .. and . segments.

ParameterTypeDefaultDescription
pathstringThe 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 .htmlpath.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.

See Also