| |

Node.js 24 🟢 URL Parsing and Manipulation with the Web URL API (node:url)

URLs are strings with structure. A URL has a protocol, a host, a port, a path, a query, and a fragment, and each part has its own rules. Treating a URL as an opaque string and manipulating it with regular expressions or string concatenation is how bugs happen: a missing slash, an unencoded space, a forgotten query parameter. Node.js has two URL APIs. The legacy url.parse() API is deprecated and emits warnings in Node.js 24. The WHATWG URL API is the standard, browser-compatible implementation, and it is the one you should be using.

This chapter covers the WHATWG URL API in node:url. You will learn the URL class, its properties for reading URL components, the URLSearchParams class for query string manipulation, and the static methods for file path conversion and safe parsing. You will also see why url.parse() is deprecated and how to migrate away from it.

Key point: The WHATWG URL class is a browser standard, implemented natively in Node.js. It parses URLs according to the WHATWG URL Standard, throws on malformed input, and provides structured access to every URL component. URLSearchParams provides read and write access to the query string, with methods for appending, deleting, and iterating parameters.


Why the WHATWG URL API exists

The legacy deprecation problem. Node.js’s original url.parse() API was written before the WHATWG URL Standard existed. Its behavior is not standardized, and it is prone to errors that have security implications . Node.js 24 emits a deprecation warning (DEP0169) whenever url.parse() is called. The warning states: “url.parse() behavior is not standardized and prone to errors that have security implications. Use the WHATWG URL API instead. CVEs are not issued for url.parse() vulnerabilities” .

The standardization problem. The WHATWG URL Standard defines exactly how a URL should be parsed, including edge cases like percent-encoding, internationalized domain names, and relative URL resolution. The legacy API predates this standard and does not follow it. The WHATWG URL class implements the standard, so its behavior is consistent across browsers, Node.js, and other runtimes .

The error-handling problem. The legacy url.parse() accepts malformed URLs and returns a partially parsed object. It almost never throws. The WHATWG URL constructor throws a TypeError on invalid input . This is a difference in philosophy: the legacy API tries to be forgiving; the WHATWG API is strict. Strictness catches bugs at the point of parsing rather than allowing malformed data to propagate.

The query string problem. The legacy API represents the query as a string. Parsing it into key-value pairs requires the separate querystring module, which has its own API and its own limitations. The WHATWG URL class exposes searchParams as a URLSearchParams object, which provides structured access to the query string with methods for reading, writing, and iterating parameters .

The file path problem. Converting between file paths and file:// URLs is a common operation in Node.js. The pathToFileURL and fileURLToPath methods handle this conversion correctly across platforms, including Windows drive letters and Unix absolute paths .


a. The URL class: parsing and reading components

The URL class is a global constructor. It is available without importing from node:url, though the module also exports it. The constructor takes a URL string and an optional base URL for resolving relative URLs.

const url = new URL('https://user:pass@example.com:8080/path?query=value#fragment');

The URL object exposes each component as a property. These properties are read-write: you can modify them and the URL string updates accordingly.

console.log(url.protocol); // 'https:'
console.log(url.username); // 'user'
console.log(url.password); // 'pass'
console.log(url.hostname); // 'example.com'
console.log(url.port);     // '8080'
console.log(url.host);     // 'example.com:8080'
console.log(url.pathname); // '/path'
console.log(url.search);   // '?query=value'
console.log(url.hash);     // '#fragment'
console.log(url.origin);   // 'https://example.com:8080'

The href property (and the toString() method) returns the full serialized URL string.

console.log(url.href); // 'https://user:pass@example.com:8080/path?query=value#fragment'

The constructor can resolve relative URLs when a base is provided:

const relative = new URL('/api/users', 'https://example.com');
console.log(relative.href); // 'https://example.com/api/users'

This is the behavior specified by the WHATWG URL Standard. The base URL provides the protocol, host, and other components that are missing from the relative URL.

Modifying a property updates the URL. The href reflects the change:

url.pathname = '/new-path';
console.log(url.href); // 'https://user:pass@example.com:8080/new-path?query=value#fragment'

The URL constructor throws on invalid input. A malformed URL, a missing protocol, or an invalid port all produce a TypeError:

try {
  new URL('not a url');
} catch (error) {
  console.log(error.name); // 'TypeError'
}

Node.js 22.1.0 added URL.parse(), a static method that returns null on failure instead of throwing. This is useful when you want to handle invalid URLs gracefully without a try/catch block .

const valid = URL.parse('https://example.com');
const invalid = URL.parse('not a url');

console.log(valid);   // URL object
console.log(invalid); // null

b. URLSearchParams: reading and writing query parameters

URLSearchParams provides structured access to a URL’s query string. It is available as url.searchParams on a URL object, and it can also be constructed independently.

const url = new URL('https://example.com/?name=Alice&age=30');
const params = url.searchParams;

console.log(params.get('name'));  // 'Alice'
console.log(params.get('age'));   // '30'
console.log(params.has('name'));  // true

The get method returns the first value for a name. If a name appears multiple times, getAll returns all values.

params.append('name', 'Bob');
console.log(params.getAll('name')); // ['Alice', 'Bob']

The append method adds a new name-value pair without removing existing ones. The set method replaces all existing pairs with the given name and adds one if none exists .

params.set('name', 'Charlie');
console.log(params.toString()); // 'name=Charlie&age=30'

The delete method removes all pairs with the given name. The has method checks for existence. The toString() method serializes the parameters to a query string .

URLSearchParams can be constructed from several input types. A string is parsed as a query string, with or without a leading ? .

const params1 = new URLSearchParams('user=abc&query=xyz');
const params2 = new URLSearchParams('?user=abc&query=xyz');

An object produces a set of parameters with string-coerced keys and values. Arrays are joined with commas .

const params = new URLSearchParams({
  user: 'abc',
  query: ['first', 'second']
});
console.log(params.toString()); // 'user=abc&query=first%2Csecond'

An iterable of key-value pairs allows duplicate keys. This is the form to use when a parameter can appear multiple times .

const params = new URLSearchParams([
  ['user', 'abc'],
  ['query', 'first'],
  ['query', 'second']
]);
console.log(params.toString()); // 'user=abc&query=first&query=second'

Iteration is supported through entries(), keys(), values(), and forEach(). The entries() method returns an iterator of [name, value] arrays .

for (const [name, value] of params) {
  console.log(name, value);
}

The sort() method sorts parameters by name in place. This is useful for cache keys and deterministic URLs .


c. File URL conversion and URLPattern

Node.js provides two functions for converting between filesystem paths and file:// URLs. pathToFileURL converts a path to a URL object. fileURLToPath converts a URL object or string to a path.

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

const fileUrl = pathToFileURL('/home/user/file.txt');
console.log(fileUrl.href); // 'file:///home/user/file.txt'

const filePath = fileURLToPath('file:///home/user/file.txt');
console.log(filePath); // '/home/user/file.txt'

The pathToFileURL function accepts an options object with a windows property for cross-platform testing. The fileURLToPath function accepts the same option. This allows testing Windows path conversion on non-Windows systems .

URLPattern, added in Node.js 23.8.0, is a Web API for matching URLs against patterns. It supports wildcards, named groups, and case-insensitive matching. The test() method returns a boolean; the exec() method returns match results with captured groups .

const pattern = new URLPattern({ pathname: '/users/:id' });

console.log(pattern.test('https://example.com/users/123')); // true
const match = pattern.exec('https://example.com/users/123');
console.log(match.pathname.groups.id); // '123'

URLPattern is experimental in Node.js 24 and is primarily useful for routing and URL filtering.


Complete Example Session

// ============================================
// PART 1: PARSING A URL
// ============================================
const url = new URL('https://user:pass@example.com:8080/path?query=value#fragment');
console.log(url.protocol); // 'https:'
console.log(url.hostname); // 'example.com'
console.log(url.port);     // '8080'
console.log(url.pathname); // '/path'
console.log(url.search);   // '?query=value'
console.log(url.hash);     // '#fragment'
// ============================================
// PART 2: RESOLVING RELATIVE URLS
// ============================================
const base = 'https://example.com/api/';
const relative = new URL('users/123', base);
console.log(relative.href); // 'https://example.com/api/users/123'
// ============================================
// PART 3: SAFE PARSING WITH URL.parse
// ============================================
const valid = URL.parse('https://example.com');
const invalid = URL.parse('not a url');

console.log(valid?.href);   // 'https://example.com/'
console.log(invalid);       // null
// ============================================
// PART 4: READING QUERY PARAMETERS
// ============================================
const url = new URL('https://example.com/?name=Alice&age=30');
console.log(url.searchParams.get('name')); // 'Alice'
console.log(url.searchParams.has('age'));  // true
// ============================================
// PART 5: MODIFYING QUERY PARAMETERS
// ============================================
const url = new URL('https://example.com/?a=1');
url.searchParams.append('a', '2');
url.searchParams.set('b', '3');
console.log(url.href); // 'https://example.com/?a=1&a=2&b=3'
// ============================================
// PART 6: DELETING PARAMETERS
// ============================================
const url = new URL('https://example.com/?a=1&b=2&c=3');
url.searchParams.delete('b');
console.log(url.href); // 'https://example.com/?a=1&c=3'
// ============================================
// PART 7: CONSTRUCTING URLSearchParams
// ============================================
const fromString = new URLSearchParams('user=abc&query=xyz');
const fromObject = new URLSearchParams({ user: 'abc', query: 'xyz' });
const fromArray = new URLSearchParams([['user', 'abc'], ['query', 'xyz']]);

console.log(fromString.toString()); // 'user=abc&query=xyz'
console.log(fromObject.toString()); // 'user=abc&query=xyz'
console.log(fromArray.toString());  // 'user=abc&query=xyz'
// ============================================
// PART 8: FILE URL CONVERSION
// ============================================
import { pathToFileURL, fileURLToPath } from 'node:url';

const fileUrl = pathToFileURL('/home/user/file.txt');
console.log(fileUrl.href); // 'file:///home/user/file.txt'

const filePath = fileURLToPath('file:///home/user/file.txt');
console.log(filePath); // '/home/user/file.txt'
// ============================================
// PART 9: URLPATTERN
// ============================================
const pattern = new URLPattern({ pathname: '/users/:id' });
console.log(pattern.test('https://example.com/users/123')); // true
console.log(pattern.test('https://example.com/posts/123')); // false
// ============================================
// PART 10: MIGRATING FROM url.parse()
// ============================================
// Legacy:
// const parsed = url.parse('https://example.com/path?query=value');
// parsed.query // 'query=value' (string)
// parsed.path  // '/path?query=value'

// WHATWG:
const parsed = new URL('https://example.com/path?query=value');
parsed.searchParams.get('query'); // 'value'
parsed.pathname + parsed.search;  // '/path?query=value'

The ten parts covered parsing a URL, resolving relative URLs, safe parsing with URL.parse, reading query parameters, modifying query parameters, deleting parameters, constructing URLSearchParams, file URL conversion, URLPattern, and migration from url.parse().


Quick Reference

URL Class Properties

PropertyDescriptionExample
hrefFull URL string'https://example.com/'
originProtocol + host'https://example.com'
protocolProtocol with colon'https:'
usernameUsername'user'
passwordPassword'pass'
hostHost with port'example.com:8080'
hostnameHost without port'example.com'
portPort'8080'
pathnamePath'/path'
searchQuery with ?'?query=value'
hashFragment with #'#fragment'
searchParamsURLSearchParams objectURLSearchParams {}

URLSearchParams Methods

MethodPurpose
get(name)First value for name
getAll(name)All values for name
has(name)Check existence
append(name, value)Add new pair
set(name, value)Replace all pairs
delete(name)Remove all pairs
sort()Sort by name
toString()Serialize to query string
entries()Iterator of pairs
forEach(fn)Iterate pairs

Static Methods

MethodPurpose
URL.parse(input, base)Safe parsing, returns null on failure
pathToFileURL(path)Convert path to file URL
fileURLToPath(url)Convert file URL to path

Legacy vs WHATWG

Aspecturl.parse()new URL()
StatusDeprecatedStandard
Error handlingRarely throwsThrows on invalid
Query accessStringURLSearchParams
StandardNoWHATWG URL
Node.js 24Deprecation warningRecommended

Best Practices

✅ Do This:

// Use the WHATWG URL class
const url = new URL('https://example.com/path');                // ✅

// Use URL.parse for safe parsing
const parsed = URL.parse(userInput);                            // ✅

// Use searchParams for query manipulation
url.searchParams.set('page', '2');                              // ✅

// Use pathToFileURL for file paths
const fileUrl = pathToFileURL('/path/to/file');                 // ✅

// Use try/catch for URL constructor
try { new URL(input); } catch { }                               // ✅

❌ Don’t Do This:

// Don't use url.parse() in new code
import url from 'node:url';
url.parse('https://example.com');                               // ❌ deprecated

// Don't manipulate query strings with string concatenation
const query = '?' + Object.entries(params).map(...).join('&');   // ❌

// Don't assume URL constructor won't throw
const url = new URL(invalidInput); // throws                       // ❌

// Don't use querystring module for URL queries
import querystring from 'node:querystring';                     // ⚠️ legacy

Common Pitfalls

PitfallWhy It HappensFix
Deprecation warningUsing url.parse()Use new URL()
TypeError on invalid URLConstructor throwsUse URL.parse() or try/catch
Query string not updatingModified search directlyUse searchParams
Relative URL failsNo base providedPass base URL as second argument
File path incorrectUsed URL for fileUse pathToFileURL
Duplicate params lostUsed object constructorUse iterable constructor

Real-World Examples

1. Parse Request URL

const url = new URL(req.url, `http://${req.headers.host}`);

2. Add Query Parameter

const url = new URL('https://api.example.com/search');
url.searchParams.set('q', 'nodejs');

3. Remove Query Parameter

url.searchParams.delete('tracking');

4. Iterate Query Parameters

for (const [key, value] of url.searchParams) {
  console.log(`${key}: ${value}`);
}

5. Build URL from Object

const url = new URL('https://example.com');
Object.entries(params).forEach(([k, v]) => url.searchParams.append(k, v));

6. File URL

import { pathToFileURL } from 'node:url';
const url = pathToFileURL('./data.json');

7. URL Pattern Matching

const pattern = new URLPattern({ pathname: '/api/:version/*' });
pattern.test('https://example.com/api/v1/users'); // true

8. Safe Parse User Input

const url = URL.parse(userInput);
if (url) { process(url); }

9. Resolve Relative Path

const absolute = new URL('../images/logo.png', pageUrl);

10. Normalize URL

const url = new URL(input);
url.hash = ''; // remove fragment

Visual

URL Components

┌─────────────────────────────────────────────────────────────┐
│  https://user:pass@example.com:8080/path?query=value#frag   │
│  ───── ──── ──── ─────────── ──── ───── ───── ───── ─────   │
│    │     │    │        │        │     │      │      │       │
│    │     │    │        │        │     │      │      └── hash│
│    │     │    │        │        │     │      └── search     │
│    │     │    │        │        │     └── pathname          │
│    │     │    │        │        └── port                    │
│    │     │    │        └── hostname                         │
│    │     │    └── password                                  │
│    │     └── username                                       │
│    └── protocol                                             │
│                                                             │
│  origin = protocol + // + hostname + port                   │
│  host   = hostname + : + port                               │
│                                                             │
└─────────────────────────────────────────────────────────────┘

URLSearchParams Operations

┌─────────────────────────────────────────────────────────────┐
│  INITIAL: ?name=Alice&age=30                                │
│                                                             │
│  get('name')        → 'Alice'                               │
│  getAll('name')     → ['Alice']                             │
│  has('age')         → true                                  │
│                                                             │
│  append('name','Bob')                                       │
│  → ?name=Alice&age=30&name=Bob                              │
│                                                             │
│  set('name','Charlie')                                      │
│  → ?name=Charlie&age=30                                     │
│                                                             │
│  delete('age')                                              │
│  → ?name=Charlie                                            │
│                                                             │
│  toString()         → 'name=Charlie'                        │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Legacy vs WHATWG Migration

┌─────────────────────────────────────────────────────────────┐
│  LEGACY (DEPRECATED)                                        │
│                                                             │
│  const parsed = url.parse('https://example.com/path?q=1');  │
│                                                             │
│  parsed.protocol  → 'https:'                                │
│  parsed.hostname  → 'example.com'                           │
│  parsed.pathname  → '/path'                                 │
│  parsed.query     → 'q=1'  (string)                         │
│  parsed.path      → '/path?q=1'                             │
│                                                             │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  WHATWG (RECOMMENDED)                                       │
│                                                             │
│  const url = new URL('https://example.com/path?q=1');       │
│                                                             │
│  url.protocol     → 'https:'                                │
│  url.hostname     → 'example.com'                           │
│  url.pathname     → '/path'                                 │
│  url.searchParams.get('q') → '1'                            │
│  url.pathname + url.search → '/path?q=1'                    │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Summary

ItemValue
URL classGlobal constructor, WHATWG standard
Deprecated APIurl.parse() — DEP0169 warning
Safe parsingURL.parse(input, base)
Query accessurl.searchParams
Query typeURLSearchParams
File conversionpathToFileURL, fileURLToPath
Pattern matchingURLPattern (experimental)
Propertiesprotocol, hostname, port, pathname, search, hash
ConstructorThrows TypeError on invalid input

Key takeaways:

  • The WHATWG URL class is the standard. It implements the WHATWG URL Standard and behaves identically in browsers and Node.js. The legacy url.parse() API is deprecated and emits warnings in Node.js 24 .
  • URL throws on invalid input; url.parse() does not. The WHATWG API is strict. Use URL.parse() (added in Node.js 22.1.0) for safe parsing that returns null on failure instead of throwing .
  • URLSearchParams provides structured query access. The searchParams property is a URLSearchParams object with methods for getting, setting, appending, deleting, and iterating query parameters. It replaces the querystring module for URL queries .
  • The constructor resolves relative URLs with a base. new URL('/path', 'https://example.com') produces https://example.com/path. This is the standard behavior and is useful for resolving links relative to a page URL.
  • pathToFileURL and fileURLToPath handle file conversions. These functions correctly convert between filesystem paths and file:// URLs, including cross-platform differences.
  • URLPattern is experimental but useful. Added in Node.js 23.8.0, it provides Web-compatible URL pattern matching for routing and filtering .

Remember: The WHATWG URL API is not just a replacement for url.parse(). It is the standard way to work with URLs in JavaScript. It is strict, structured, and consistent across environments. Use new URL() for parsing, searchParams for query manipulation, and URL.parse() for safe parsing of untrusted input. The legacy API is deprecated for good reason: its behavior is not standardized, and it is prone to security-relevant errors. Migrate to the WHATWG API and the deprecation warnings will stop.



Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!