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.jsPath 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.txtNormalize 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/folder3If 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/folder3Join 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.jsBut 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 pathVerify 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: trueAn 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: falseExtract 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.jsIf 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); // .jsExtract 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#hashModify 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: 99Update property
myURL.searchParams.set('id', '123');
console.log('myURL.href :>> ', myURL.href);
// output: https://jsonplaceholder.typicode.com/todos?id=123Append property
myURL.searchParams.append('userId', '789');
console.log('myURL.href :>> ', myURL.href);
// output: https://jsonplaceholder.typicode.com/todos?id=99&userId=789Sort 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=99Delete property
myURL.searchParams.delete('id');
console.log('myURL.href :>> ', myURL.href);
// output: https://jsonplaceholder.typicode.com/todosYou 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=789Serialize 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%2FSimilarly, 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%2F000The 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%2FWorldIf you need it back in its original form, you can invoke unescape().
const decoded = querystring.unescape(encoded);
console.log('decoded :>> ', decoded);
// output: Hello/WorldOS-Specific Paths
There are also OS-Specific Path sub-modules:
path-posixpath-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 😎
