File paths look simple until you have to handle them programmatically across different operating systems, different working directories, and different ways a path can be written. The path module is Node.js's solution to this — a set of utilities for working with file and directory paths correctly, regardless of the platform the code runs on.
It is a small module, but it is used constantly. Almost any code that touches the file system — reading a config file, serving a static asset, resolving an import — benefits from using path instead of manually concatenating strings.
Why Not Just Use String Concatenation?
It is tempting to build a file path by joining strings directly:
const filepath = directory + '/' + filename;
This breaks in several ways. On Windows, the path separator is a backslash (\), not a forward slash. If directory already ends in a slash, you get a doubled separator (folder//file.txt). If filename contains ../ to navigate up a directory, naive concatenation does not resolve it correctly, which can become a security issue when filenames come from user input.
The path module exists specifically to handle these cases correctly and consistently, so you never have to think about separator characters or platform differences yourself.
Importing the Module
const path = require('path');
path is a core module — no installation required, available in every Node.js environment.
Joining Paths — path.join()
path.join() combines multiple path segments into a single path, inserting the correct separator for the current operating system and normalising the result.
const filepath = path.join('uploads', '2026', 'june', 'report.pdf');
console.log(filepath);
// On Linux/macOS: uploads/2026/june/report.pdf
// On Windows: uploads\2026\june\report.pdf
path.join() also resolves .. and . segments and collapses redundant separators:
console.log(path.join('uploads', '..', 'images', 'photo.png'));
// images/photo.png
console.log(path.join('uploads/', '/temp/', '/file.txt'));
// uploads/temp/file.txt — extra slashes are cleaned up
This is the method you reach for by far the most often when constructing a path from multiple pieces.
Resolving Absolute Paths — path.resolve()
path.resolve() builds an absolute path, working from right to left, treating each segment as if it were a cd command, until an absolute path is constructed. If no segment is itself absolute, the current working directory is used as the starting point.
console.log(path.resolve('uploads', 'report.pdf'));
// /home/wariz/project/uploads/report.pdf
// (assuming the current working directory is /home/wariz/project)
console.log(path.resolve('/uploads', 'report.pdf'));
// /uploads/report.pdf — an absolute segment short-circuits the resolution
path.join() vs path.resolve()
This is the distinction that matters most when choosing between the two:
path.join() | path.resolve() | |
|---|---|---|
| Result | A relative or absolute path, depending on input | Always an absolute path |
| Behaviour | Concatenates segments and normalises them | Resolves from right to left, like navigating directories |
| Starting point | None — purely string-based joining | Current working directory, unless an absolute segment is given |
console.log(path.join('uploads', 'report.pdf'));
// uploads/report.pdf — relative, as given
console.log(path.resolve('uploads', 'report.pdf'));
// /home/wariz/project/uploads/report.pdf — always absolute
In practice, path.resolve() combined with __dirname (or import.meta.dirname in modern ESM) is the standard way to build a reliable absolute path regardless of where the script is run from:
const configPath = path.resolve(__dirname, 'config.json');
This guarantees the path is correct relative to the file's own location, not relative to whatever directory the process happened to be started from — a common source of bugs when a script is run from a different working directory than expected.
Extracting Parts of a Path
Several path methods extract specific components from a full path string.
const filepath = '/home/wariz/project/uploads/report.pdf';
console.log(path.basename(filepath)); // report.pdf
console.log(path.basename(filepath, '.pdf')); // report — extension removed
console.log(path.dirname(filepath)); // /home/wariz/project/uploads
console.log(path.extname(filepath)); // .pdf
path.basename() returns the final segment of the path — typically the filename. Passing a second argument removes that extension from the result if present.
path.dirname() returns everything except the final segment — the containing directory.
path.extname() returns just the file extension, including the leading dot.
path.parse() — All Components at Once
path.parse() breaks a path into its full set of components in a single call:
const parsed = path.parse('/home/wariz/project/uploads/report.pdf');
console.log(parsed);
// {
// root: '/',
// dir: '/home/wariz/project/uploads',
// base: 'report.pdf',
// ext: '.pdf',
// name: 'report'
// }
| Property | Meaning |
|---|---|
root | The root of the filesystem (/ on Linux/macOS, C:\ on Windows) |
dir | The directory containing the file |
base | The filename including its extension |
ext | Just the extension |
name | The filename without its extension |
path.format() — the Reverse Operation
path.format() takes an object in the same shape returned by path.parse() and builds a path string from it:
const filepath = path.format({
dir: '/home/wariz/project/uploads',
name: 'report',
ext: '.pdf',
});
console.log(filepath); // /home/wariz/project/uploads/report.pdf
Checking If a Path Is Absolute
console.log(path.isAbsolute('/home/wariz/project')); // true
console.log(path.isAbsolute('uploads/report.pdf')); // false
console.log(path.isAbsolute('C:\\Users\\wariz')); // true (Windows)
This is useful when accepting a path as input — from a config file, a CLI argument, or a request — and needing to decide whether to resolve it relative to a known base directory or treat it as already complete.
Cross-Platform Considerations
path.sep
The separator character used by the current operating system:
console.log(path.sep); // '/' on Linux/macOS, '\' on Windows
path.delimiter
The character used to separate multiple paths in an environment variable like PATH:
console.log(path.delimiter); // ':' on Linux/macOS, ';' on Windows
path.posix and path.win32
Every method on the path module is normally platform-aware — it behaves according to the operating system the code is currently running on. If you specifically need POSIX-style (Linux/macOS) or Windows-style path handling regardless of the current platform — for example, when processing a path that was generated on a different system than the one your code is running on — path.posix and path.win32 provide explicit, platform-independent versions:
console.log(path.win32.join('uploads', 'report.pdf'));
// uploads\report.pdf — always Windows-style, regardless of current OS
console.log(path.posix.join('uploads', 'report.pdf'));
// uploads/report.pdf — always POSIX-style, regardless of current OS
This matters in practice when, for example, a server running on Linux needs to generate a Windows-compatible file path to include in a downloadable file or a response sent to a Windows client.
A Practical Example — Serving Static Files Safely
A common real-world use of path is constructing a safe file path from user input — for example, serving a requested file while preventing access outside an intended directory (a vulnerability known as path traversal).
const path = require('path');
const fs = require('fs/promises');
const UPLOADS_DIR = path.resolve(__dirname, 'uploads');
async function serveFile(requestedFilename) {
const requestedPath = path.join(UPLOADS_DIR, requestedFilename);
// Ensure the resolved path is still inside UPLOADS_DIR
if (!requestedPath.startsWith(UPLOADS_DIR)) {
throw new Error('Invalid file path');
}
return fs.readFile(requestedPath);
}
Without this check, a malicious filename like ../../etc/passwd passed in as requestedFilename could escape the intended uploads directory entirely. path.join() will normalise the .. segments, but it will not stop the resulting path from pointing outside the intended directory — that check is the developer's responsibility, and comparing the resolved path against the known safe base directory is the standard way to enforce it.
Summary
| Task | Method |
|---|---|
| Join path segments | path.join(...segments) |
| Build an absolute path | path.resolve(...segments) |
| Get the filename | path.basename(filepath) |
| Get the containing directory | path.dirname(filepath) |
| Get the file extension | path.extname(filepath) |
| Break a path into components | path.parse(filepath) |
| Build a path from components | path.format(pathObject) |
| Check if a path is absolute | path.isAbsolute(filepath) |
| Get the OS path separator | path.sep |
| Force POSIX-style paths | path.posix.method(...) |
| Force Windows-style paths | path.win32.method(...) |