iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

url / URLSearchParams

The node:url module gives you the same URL + URLSearchParams API the browser has, plus extras: fileURLToPath for ESM __dirname, format/resolve for legacy code, and domainToASCII for IDN. Reach for it any time you build, parse, or normalise URLs.

URL, searchParams, fileURLToPath, format

EXAMPLE
import { URL, URLSearchParams, fileURLToPath, pathToFileURL, domainToASCII } from 'node:url';
import path from 'node:path';

// 1) Parse a URL — same API as the browser
const u = new URL('https://user:pw@example.com:8443/path/to/x?q=1&q=2&page=3#section');
u.protocol;        // 'https:'
u.username;        // 'user'
u.password;        // 'pw'
u.hostname;        // 'example.com'
u.host;            // 'example.com:8443'
u.port;            // '8443'
u.pathname;        // '/path/to/x'
u.search;          // '?q=1&q=2&page=3'
u.searchParams;    // URLSearchParams instance
u.hash;            // '#section'
u.origin;          // 'https://example.com:8443'

// 2) Build a URL — much safer than string concat
const api = new URL('/v1/users', 'https://api.example.com');
api.searchParams.set('limit', 50);
api.searchParams.append('tag', 'admin');
api.searchParams.append('tag', 'staff');
api.toString();    // 'https://api.example.com/v1/users?limit=50&tag=admin&tag=staff'

// 3) URLSearchParams — also constructible directly
const p = new URLSearchParams({ limit: 10, page: 2 });
p.set('q', 'hello world');
p.toString();      // 'limit=10&page=2&q=hello+world'
p.get('q');        // 'hello world'
p.getAll('tag');   // []  (array form)
p.has('q');        // true
p.delete('q');
for (const [k, v] of p) { /* iterate */ }

// Parse a query string into an object
Object.fromEntries(new URLSearchParams('a=1&b=2'));   // { a: '1', b: '2' }

// 4) Resolve a relative URL
new URL('users/42', 'https://api.example.com/v1/').toString();
// 'https://api.example.com/v1/users/42'
new URL('../', 'https://api.example.com/v1/users/').toString();
// 'https://api.example.com/v1/'
new URL('?reset', api).toString();   // keeps base, replaces query

// 5) Validate / safely allowlist schemes
function safeUrl(input, base) {
    try {
        const u = new URL(input, base);
        if (!['http:', 'https:'].includes(u.protocol)) return null;
        return u.toString();
    } catch { return null; }
}

safeUrl('https://example.com');     // valid
safeUrl('javascript:alert(1)');     // null
safeUrl('not a url');                // null

// 6) ESM __dirname + __filename
const __filename = fileURLToPath(import.meta.url);
const __dirname  = path.dirname(__filename);
const staticDir  = path.join(__dirname, 'public');

// pathToFileURL — opposite direction
const fileUrl = pathToFileURL('/etc/hosts');   // file:///etc/hosts

// 7) IDN domains
domainToASCII('münchen.de');          // 'xn--mnchen-3ya.de'  (Punycode)
// The URL constructor handles IDN automatically; this exposes the conversion if you need it.

// 8) Legacy url.format / url.parse — AVOID for new code
// The legacy 'url' API (url.parse, url.format) parsed differently than the WHATWG URL spec.
// Use new URL() everywhere in modern Node.

// 9) Encoding pitfalls
encodeURIComponent('hello world');       // 'hello%20world'
encodeURI('https://example.com/a b');    // 'https://example.com/a%20b'
// URLSearchParams encodes spaces as '+' — but new URL() can read either.

// 10) Building external API URLs in HTTP clients
const response = await fetch(api);
// Most HTTP libraries accept URL instances directly — pass new URL() over string concat.

// 11) Detecting if a URL is reachable from your server (SSRF safety)
function isInternalUrl(input) {
    const u = new URL(input);
    return ['localhost', '127.0.0.1', '0.0.0.0', '::1'].includes(u.hostname)
        || u.hostname.endsWith('.internal')
        || u.hostname.match(/^10\\.|^172\\.(1[6-9]|2\\d|3[01])\\.|^192\\.168\\./);
}
// Block internal URLs from server-side fetches. Resolve DNS in your fetch wrapper to be thorough.

// 12) Modifying a URL immutably
function withParam(input, key, value) {
    const u = new URL(input);
    u.searchParams.set(key, value);
    return u.toString();
}
withParam('https://example.com?a=1', 'b', '2');
// 'https://example.com/?a=1&b=2'

// 13) Comparing URLs
function sameOrigin(a, b) {
    return new URL(a).origin === new URL(b).origin;
}

// 14) Common bugs
// • Concatenating URLs with template literals — encoding wrong, double slashes, broken on Windows paths
// • Treating new URL() as throwing on bad protocol — only throws on UNPARSEABLE input; allowlist schemes
// • URLSearchParams keys are case-sensitive — be consistent
// • path-style queries (matrix params /a;k=v) — not supported by URL; parse manually
// • Forgetting to round-trip through URL when comparing — '/foo' vs '/foo/' differ
// • IPv6 hostnames need brackets: 'http://[::1]:3000'
// • String concatenation for query strings — always use URLSearchParams

Why it matters

Use new URL() + URLSearchParams for every URL operation: parse, build, normalise, validate. Recover ESM __dirname with fileURLToPath(import.meta.url), allowlist URL schemes for safety, and block private-IP destinations on server-side fetches to neutralise SSRF.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
const u = new URL('https://example.com/p?q=hi');
u.searchParams.get('q');   // 'hi'
u.pathname = '/other';
Try it Yourself »

Discussion

Loading…