Working with Paths in Node.js
One of the more useful skill sets in Node.js is to learn to work with Paths.
In this article, you’ll learn how to read, parse, join, and alter Paths/URLs in a cross-OS manner.
We’ll make use of three of the core Node.js modules:
As well as some built-in globals.
Built-in Globals
In a Node.js application, you can read the current working directory and current file name using __dirname
and __filename
globals.
Create a JavaScript file on your machine (e.g. app.js) and paste the two logs below:
// #1 current working directory
console.log(__dirname);
// #2 current working file
console.log(__filename);
Upon running the file ($ node app.js
), the paths will be printed in the console:
// #1 current working directory
rootFolder/folder1/folder2/currentFolder
// #2 current working file
rootFolder/.../previous/currentFolder/app.js
Path Parsing
The Path module is used to read and manage file paths.
For example, you can take the existing file name (using __filename
) and parse it into an object:
const path = require('path');
const parsedPath = path.parse(__filename);
console.log('parsedPath :>> ', parsedPath);
Assuming you’ve created an app.js file on your Desktop, the output would be like this (Windows):
{
root: 'C:\\', // ROOT_DIRECTORY
dir: 'C:\\Users\\Desktop', // FULL_PATH
base: 'app.js', // FILE_NAME_WITH_EXTENSION
ext: '.js', // FILE_EXTENSION
name: 'app' // FILE_NAME_WITHOUT_EXTENSION
}
This can be reverse-engineered to construct a URL from an object using path.format()
:
// if "dir" and "base" are set, "root" is ignored
const constructedPath = path.format({
root: 'ignored-path',
dir: '/home/user/batman',
base: 'bio.txt',
});
console.log('constructedPath :>> ', constructedPath);
// output: /home/user/batman/bio.txt
// Alternatively
// if "dir" is not specified, "root" will be visible
const constructedPath2 = path.format({
root: 'root-user/',
base: 'bio.txt',
});
console.log('constructedPath2 :>> ', constructedPath2);
// output: root-user/bio.txt
Normalize Paths
Normalization resolves the //
, .
and ..
separators of the path to their correct form.
const path = require('path');
const normalizeDots = path.normalize('/folder1/folder2/../folder3')
console.log('normalizeDots :>> ', normalizeDots);
// output: normalizeDots :>> /folder1/folder3
If the method encounters multiple path separators, it replaces all of them with a single platform-specific path separator.
const normalizeSlash = path.normalize("/folder1///folder2///folder3")
console.log('normalizeSlash :>> ', normalizeSlash);
// output: normalizeSlash :>> /folder1/folder2/folder3
Join Paths
You can combine paths in one of two ways:
path.join()
path.resolve()
When you need to import something from another file in your application, it’s recommended to use one of these functions to join paths (as they are cross-OS friendly) rather than manually navigating by constructing a string.
// Example of reading file content by first locating the file
// on your machine
const fs = require('fs');
const path = require('path');
const filePath = path.join(__dirname, './PATH_TO_FILE/FILE_NAME.txt');
const content = fs.readFileSync(filePath, 'utf8');
console.log('content:>> ', content);
You can combine file paths in two ways:
const joinedPath = path.join('../', './http/http.js');
const resolvedPath = path.resolve('../', './http/http.js');
The difference between the two is that path.resolve()
returns an absolute path, while path.join()
just joins paths like strings.
console.log('joinedPath :>> ', joinedPath);
// output: joinedPath :>> ..\http\http.js
console.log('resolvedPath :>> ', resolvedPath);
// output: resolvedPath :>> Root-Directory:\folder1\http\http.js
But if you prefix the path with a built-in global (__dirname
), as it’s done when the reading file in the example above, the output will be an absolute path:
path.join(__dirname, './PATH_TO_FILE/FILE_NAME.txt')
// output: absolute path
Verify Absolute Paths
The Path module also has a method (isAbsolute()
) to check if the path is absolute or not:
const path = require('path');
const filePath = path.join(__dirname, './PATH_TO_FILE/FILE_NAME.txt')
const isAbsolute = path.isAbsolute(filePath);
console.log('isAbsolute :>> ', isAbsolute);
// output: true
An absolute path is defined as a path that contains the complete details needed to locate a file.
const dummyURL1 = '/user/batman/bio.txt';
const dummyURL2 = 'user/batman/bio.txt';
const isAbsolute1 = path.isAbsolute(dummyURL1); // output: true
const isAbsolute2 = path.isAbsolute(dummyURL2); // output: false
Extract specific data from the path
To extract specific parts from the path (without previously parsing), you can use the following:
const path = require('path');
// Extract the folder name
const folderName = path.basename(__dirname);
console.log('folderName :>> ', folderName); // FOLDER_NAME
// Extract the file name
const fileName = path.basename(__filename);
console.log('fileName :>> ', fileName); // FILE_NAME.js
If you need to extract an extension from a file, you do not need to write some fancy regular expression. Instead, use path.extname()
as demonstrated below:
// Extract the extension
const fileExtension = path.extname(__filename);
console.log('fileExtension :>> ', fileExtension); // .js
Extract URLs
The URL module can split the URL into readable parts.
You can take a URL of your website, e.g: https://jsonplaceholder.typicode.com/todos?id=1
, and then parse it into a JavaScript object:
const url = require('url');
const JSON_PLACEHOLDER_URL = 'https://jsonplaceholder.typicode.com/todos?id=1';
const parsedURL = url.parse(JSON_PLACEHOLDER_URL, true);
console.log('parsedURL :>> ', parsedURL);
Url {
protocol: 'https:',
slashes: true,
auth: null,
host: 'jsonplaceholder.typicode.com',
port: null,
hostname: 'jsonplaceholder.typicode.com',
hash: null,
search: '?id=1',
query: [Object: null prototype] { id: '1' },
pathname: '/todos',
path: '/todos?id=1',
href: 'https://jsonplaceholder.typicode.com/todos?id=1'
}
Then you can extract specific parts:
console.log('parsedURL.hostname :>> ', parsedURL.hostname);
console.log('parsedURL.protocol :>> ', parsedURL.protocol);
console.log('parsedURL.query :>> ', parsedURL.query);
console.log('parsedURL.pathname :>> ', parsedURL.pathname);
Once again, you can do the vice-versa — Construct a URL from an object:
const urlObject = {
protocol: 'http',
hostname: 'localhost',
port: 8000,
pathname: '/api/users',
hash: '#hash'
};
const url = require('url');
// Construct the URL
const objectToURL = url.format(urlObject);
console.log('objectToURL :>> ', objectToURL);
// output: http://localhost:8000/api/users#hash
Modify the URL
You can read, add, update, or delete existing properties in the URL using the searchParams
property on the URL instance.
const { URL } = require('url');
const JSON_PLACEHOLDER_URL = 'https://jsonplaceholder.typicode.com/todos?id=99';
const myURL = new URL(JSON_PLACEHOLDER_URL);
Read property
console.log(myURL.searchParams.get('id'));
// output: 99
Update property
myURL.searchParams.set('id', '123');
console.log('myURL.href :>> ', myURL.href);
// output: https://jsonplaceholder.typicode.com/todos?id=123
Append property
myURL.searchParams.append('userId', '789');
console.log('myURL.href :>> ', myURL.href);
// output: https://jsonplaceholder.typicode.com/todos?id=99&userId=789
Sort properties
Reorder properties based on the alphabetical order:
myURL.searchParams.append('abc', '789');
myURL.searchParams.sort();
console.log('myURL.href :>> ', myURL.href);
// output: https://jsonplaceholder.typicode.com/todos?abc=789&id=99
Delete property
myURL.searchParams.delete('id');
console.log('myURL.href :>> ', myURL.href);
// output: https://jsonplaceholder.typicode.com/todos
You can also create custom searchParams
using URLSearchParams
class instance that accepts a string, object, or an iterable:
const { URLSearchParams } = require('url');
const params = new URLSearchParams([
['player', 'soap'],
['id', '99'],
['userId', '789'],
]);
console.log(params.toString());
// output: player=soap&id=99&userId=789
Serialize Query String
The Query String module is used to manipulate the query string.
For example, if you have a JavaScript object:
const user = {
name: 'Mirza',
website: 'https://mirzaleka.medium.com/'
};
You can serialize it into a query string:
const querystring = require('querystring');
const objectToQuery = querystring.stringify(user);
console.log('objectToQuery :>> ', objectToQuery);
// output: name=Mirza&website=https%3A%2F%2Fmirzaleka.medium.com%2F
Similarly, you can take a query string and parse it back to an object:
const objectToQuery = 'name=Mirza&website=https%3A%2F%2Fmirzaleka.medium.com%2F';
const parsed = querystring.parse(objectToQuery);
console.log('parsed :>> ', parsed);
// output: { name: 'Mirza', website: 'https://mirzaleka.medium.com/' }
Escape Characters
Another thing you can do is escape invalid query characters.
For example, if you put a string containing “/” into a URL, the HTTP will automatically escape it and parse into %2F:
Data sent: ABC/000
Data received: ABC%2F000
The Query String module works just the same:
const querystring = require('querystring');
const originalString = 'Hello/World'
const encoded = querystring.escape(originalString);
console.log('encoded :>> ', encoded);
// output: Hello%2FWorld
If you need it back in its original form, you can invoke unescape()
.
const decoded = querystring.unescape(encoded);
console.log('decoded :>> ', decoded);
// output: Hello/World
OS-Specific Paths
There are also OS-Specific Path sub-modules:
path-posix
path-win32
The path.posix
property provides access to POSIX-specific implementations of the path
methods, while the path.win32
provides access to Windows-specific implementations.
Each can be imported like:
const pathPosix = require('path/posix');
const pathWindows = require('path/win32');
I have spent hours developing custom-made solutions for features that already exist, built into Node.js.
Don’t make the same mistakes 😎