Working with Paths in Node.js

Mirza Leka
6 min readDec 27, 2023

--

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');

--

--

Mirza Leka
Mirza Leka

Written by Mirza Leka

Web Developer. DevOps Enthusiast. I share my experience with the rest of the world. Follow me on https://twitter.com/mirzaleka for news & updates #FreePalestine

No responses yet