Skip to content
Node.js nd core 4 min read

The url Module & WHATWG URL

Almost every server, client, and CLI tool eventually needs to take a URL apart, change a piece of it, or build one from scratch. Node.js exposes two ways to do this: the modern, standards-based WHATWG URL API — the same URL and URLSearchParams classes you already know from the browser — and a legacy url.parse() function that predates them. New code should reach for the WHATWG API almost every time, because it is consistent across platforms, safer about parsing, and fully spec-compliant.

The WHATWG URL class

The URL class is a global in Node.js, so you do not even need to import anything to use it. You construct it from an absolute URL string (optionally with a base), and it parses the input into structured, individually accessible components.

const url = new URL('https://user:[email protected]:8443/v2/users?role=admin&active=true#results');

console.log(url.protocol);   // https:
console.log(url.hostname);   // api.example.com
console.log(url.port);       // 8443
console.log(url.pathname);   // /v2/users
console.log(url.search);     // ?role=admin&active=true
console.log(url.hash);       // #results
console.log(url.username);   // user
console.log(url.origin);     // https://api.example.com:8443

Output:

https:
api.example.com
8443
/v2/users
?role=admin&active=true
#results
user
https://api.example.com:8443

The components are not read-only — assigning to them mutates and re-serializes the URL automatically, which makes building URLs from a template both safe and readable.

const url = new URL('https://example.com');
url.pathname = '/search';
url.searchParams.set('q', 'node url');
url.searchParams.set('page', '2');

console.log(url.href);

Output:

https://example.com/search?q=node+url&page=2

Constructing a URL from a relative path throws a TypeError unless you supply a base: new URL('/path', 'https://example.com'). Wrap untrusted input in URL.canParse(input) (Node 19.9+) to validate without try/catch.

URLSearchParams and query strings

The search property gives you the raw query string, but you rarely want to parse that by hand. Every URL exposes a live searchParams object — an instance of the global URLSearchParams class — that handles percent-encoding, repeated keys, and ordering for you.

const url = new URL('https://shop.example.com/items?tag=sale&tag=new&limit=20');

console.log(url.searchParams.get('limit'));      // 20
console.log(url.searchParams.getAll('tag'));     // [ 'sale', 'new' ]
console.log(url.searchParams.has('tag'));        // true

url.searchParams.append('tag', 'featured');
url.searchParams.delete('limit');
console.log(url.search);                         // ?tag=sale&tag=new&tag=featured

Output:

20
[ 'sale', 'new' ]
true
?tag=sale&tag=new&tag=featured

You can also use URLSearchParams standalone — to parse a query string, build a request body, or iterate every pair.

const params = new URLSearchParams({ q: 'hello world', safe: 'true' });
console.log(params.toString());          // q=hello+world&safe=true

for (const [key, value] of params) {
  console.log(`${key} -> ${value}`);
}

Output:

q=hello+world&safe=true
q -> hello world
safe -> true

URLSearchParams encodes spaces as + (form-encoding) rather than %20. Both decode correctly, but if a downstream system requires %20, build the query through url.pathname/manual encoding with encodeURIComponent instead.

File URLs: fileURLToPath and pathToFileURL

Inside ES modules there is no __dirname or __filename; instead you get import.meta.url, which is a file:// URL string. Converting between file URLs and ordinary file-system paths is where fileURLToPath and pathToFileURL from node:url come in. Never strip the file:// prefix by hand — doing so breaks on Windows (drive letters) and on paths containing spaces or non-ASCII characters.

import { fileURLToPath, pathToFileURL } from 'node:url';
import { dirname, join } from 'node:path';

// Recreate __filename / __dirname in an ES module
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
console.log(__dirname);

// Go the other way: build a file URL from a path
const fileUrl = pathToFileURL(join(__dirname, 'data', 'config.json'));
console.log(fileUrl.href);

Output:

/home/alice/project/src
file:///home/alice/project/src/data/config.json

This conversion is essential when you dynamically import() a local file by path, since the dynamic import specifier must be a valid URL.

Legacy url.parse vs the WHATWG API

Before the WHATWG URL class, Node offered url.parse(), which returns a plain object. It is now legacy and carries real footguns: it does not percent-decode consistently, is lenient about malformed input in ways that have caused security bugs, and exposes a different property shape. Use it only when maintaining old code.

import url from 'node:url';

// Legacy — avoid in new code
const legacy = url.parse('https://example.com/p?x=1', true);
console.log(legacy.query);     // { x: '1' }  (when parseQueryString = true)

// Modern equivalent
const modern = new URL('https://example.com/p?x=1');
console.log(Object.fromEntries(modern.searchParams)); // { x: '1' }

Output:

[Object: null prototype] { x: '1' }
{ x: '1' }
AspectWHATWG URLLegacy url.parse()
StandardWHATWG URL spec (browser-compatible)Node-specific, historical
AvailabilityGlobal, no import needednode:url, deprecated
Query handlingLive searchParams objectPlain object via parseQueryString
EncodingSpec-correct, consistentInconsistent, error-prone
StatusRecommendedLegacy / discouraged

Best Practices

  • Default to the global URL and URLSearchParams classes; treat url.parse() as maintenance-only.
  • Validate input with URL.canParse(str) (or a try/catch) before constructing, since invalid URLs throw.
  • Always pass a base URL when the input may be relative: new URL(path, base).
  • Use searchParams.set/append/getAll instead of string-concatenating query parameters by hand.
  • Convert between file:// URLs and paths with fileURLToPath / pathToFileURL — never manual string surgery.
  • Combine URL with node:path for file work, but never use node:path to manipulate the path portion of a network URL.
Last updated June 14, 2026
Was this helpful?