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

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