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
| Property | Description | Example |
|---|---|---|
href | Full URL string | 'https://example.com/' |
origin | Protocol + host | 'https://example.com' |
protocol | Protocol with colon | 'https:' |
username | Username | 'user' |
password | Password | 'pass' |
host | Host with port | 'example.com:8080' |
hostname | Host without port | 'example.com' |
port | Port | '8080' |
pathname | Path | '/path' |
search | Query with ? | '?query=value' |
hash | Fragment with # | '#fragment' |
searchParams | URLSearchParams object | URLSearchParams {} |
URLSearchParams Methods
| Method | Purpose |
|---|---|
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
| Method | Purpose |
|---|---|
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
| Aspect | url.parse() | new URL() |
|---|---|---|
| Status | Deprecated | Standard |
| Error handling | Rarely throws | Throws on invalid |
| Query access | String | URLSearchParams |
| Standard | No | WHATWG URL |
| Node.js 24 | Deprecation warning | Recommended |
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
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Deprecation warning | Using url.parse() | Use new URL() |
TypeError on invalid URL | Constructor throws | Use URL.parse() or try/catch |
| Query string not updating | Modified search directly | Use searchParams |
| Relative URL fails | No base provided | Pass base URL as second argument |
| File path incorrect | Used URL for file | Use pathToFileURL |
| Duplicate params lost | Used object constructor | Use 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
| Item | Value |
|---|---|
| URL class | Global constructor, WHATWG standard |
| Deprecated API | url.parse() — DEP0169 warning |
| Safe parsing | URL.parse(input, base) |
| Query access | url.searchParams |
| Query type | URLSearchParams |
| File conversion | pathToFileURL, fileURLToPath |
| Pattern matching | URLPattern (experimental) |
| Properties | protocol, hostname, port, pathname, search, hash |
| Constructor | Throws TypeError on invalid input |
Key takeaways:
- The WHATWG
URLclass is the standard. It implements the WHATWG URL Standard and behaves identically in browsers and Node.js. The legacyurl.parse()API is deprecated and emits warnings in Node.js 24 . URLthrows on invalid input;url.parse()does not. The WHATWG API is strict. UseURL.parse()(added in Node.js 22.1.0) for safe parsing that returnsnullon failure instead of throwing .URLSearchParamsprovides structured query access. ThesearchParamsproperty is aURLSearchParamsobject with methods for getting, setting, appending, deleting, and iterating query parameters. It replaces thequerystringmodule for URL queries .- The constructor resolves relative URLs with a base.
new URL('/path', 'https://example.com')produceshttps://example.com/path. This is the standard behavior and is useful for resolving links relative to a page URL. pathToFileURLandfileURLToPathhandle file conversions. These functions correctly convert between filesystem paths andfile://URLs, including cross-platform differences.URLPatternis 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!