path.join() joins path segments with the right separator and cleans up .. and duplicate slashes, but keeps the result relative if you started with a relative segment. path.resolve() does the same work from right to left and always returns an absolute path: it starts from the current working directory, and any absolute segment along the way restarts the path. Use join to build a path, and resolve when you need an absolute one.
It printed users/ada/notes.txt, then users/grace/todo.txt (the .. removed ada), then /srv/app/public/index.html and src/lib/utils.js: extra slashes and empty segments are gone. join never treats /public/ as a fresh start; it is just the next segment.
path.join vs path.resolve
The difference shows when a segment starts with / or when the first segment is relative:
With /public in the middle, join produced /srv/app/public/img while resolve threw away everything before it and produced /public/img; both handled .. the same way. The last two lines show that join kept public/index.html relative, while resolve made it absolute by putting the current folder in front.
path.join | path.resolve | |
|---|---|---|
| Result | Relative or absolute, like the input | Always absolute |
A segment starting with / | Just another segment | Restarts the path there |
| Starting point | None | The current working directory |
| No arguments | '.' | The current working directory |
| Typical use | Building a path from parts | Turning user input or config into an absolute path |
basename, dirname, extname and parse
These take a path apart. They only look at the string: the file does not have to exist.
It printed cat.final.jpg, cat.final, /home/ada/photos and .jpg: extname takes only the part after the last dot. path.parse returns all of it at once (root, dir, base, ext, name), and path.format turns such an object back into a string.
__dirname and __filename
In a CommonJS file, __dirname is the absolute path of the folder that contains the file, and __filename the file itself. A relative path like 'data.json' is relative to wherever the user ran node, which breaks as soon as someone starts your script from another folder. Build paths from __dirname instead:
It printed config.json dark and index.js. The full paths differ on every machine, which is exactly why you build them with path.join(__dirname, ...) rather than typing them.
__dirname in ES modules: import.meta.dirname
ES modules have no __dirname; using it throws ReferenceError: __dirname is not defined in ES module scope. Since Node.js 20.11, import.meta.dirname and import.meta.filename replace them:
It printed index.js and data/users.json. On Node.js versions before 20.11, derive the folder from the module URL: path.dirname(fileURLToPath(import.meta.url)), with fileURLToPath from node:url.
Why not join paths with strings
'data' + '/' + file works on macOS and Linux, then fails on Windows, where the separator is \, and it does nothing about .., doubled slashes or a missing slash. path uses the rules of the system it runs on. You can see the Windows result from any machine with path.win32:
const path = require('path');
path.posix.join('users', 'ada', 'notes.txt'); // 'users/ada/notes.txt'
path.win32.join('users', 'ada', 'notes.txt'); // 'users\\ada\\notes.txt'
path.sep; // '/' on macOS and Linux, '\\' on Windows
Pass the result of path.join to fs.readFile and the same code reads the file on every system.
Common mistakes
Expecting extname to know about double extensions. path.extname('archive.tar.gz') is '.gz', and a dotfile such as .env has no extension at all: path.extname('.env') is ''. Check the end of the name yourself when the full suffix matters.
Trusting join to keep a user inside a folder. path.join('/srv/app', '../../etc/passwd') is '/etc/passwd'. Before you read a file named by a request, resolve the path and check that it still starts with your folder.
Frequently Asked Questions
What is the difference between __dirname and process.cwd()?
__dirname is the folder of the current source file and never changes. process.cwd() is the folder the user ran node from. They are the same only when the program is started from its own folder.
Should I use path.join or path.resolve with __dirname?
Either works for path.join(__dirname, 'data.json'), since __dirname is already absolute. Use resolve when a segment might be absolute itself, such as a path the user typed, and you want that path to win.
How do I use the path module with URLs?
Do not: URLs always use / and have their own rules for .. and query strings. Use the URL class, as in new URL('../img/logo.png', 'https://example.com/docs/page').