URL and URLSearchParams are globals in Node — the WHATWG classes the browser exposes, parsed by the C++ library Ada — and the only correct way to take an address apart: split('?') breaks on encoded slashes, repeated parameters and internationalized domains. A URL is always absolute, so the second argument is the base for a relative path.
const u = new URL('/books?tag=node&tag=web&page=2#top', 'https://api.example.com:8443');
console.log(u.host, '|', u.pathname, '|', u.searchParams.getAll('tag'));
u.searchParams.set('page', '3'); u.searchParams.append('sort', 'title asc');
console.log(u.href);
console.log(URL.canParse('not a url'), new URL('https://MÜNCHEN.example/a/../café').href);api.example.com:8443 | /books | [ 'node', 'web' ] https://api.example.com:8443/books?tag=node&tag=web&page=3&sort=title+asc#top false https://xn--mnchen-3ya.example/caf%C3%A9
searchParams is live, so mutating it rewrites u.href, and the space in title asc becomes +: URLSearchParams serializes application/x-www-form-urlencoded. getAll exists because ?tag=node&tag=web is legal and get returns only the first. The parser normalizes the rest — host lowercased, .. collapsed, Unicode host to Punycode — and URL.canParse() screens untrusted input without a try. pathname stays percent-encoded: decodeURIComponent on a whole path turns %2F into a real slash, the path-traversal opening of Untrusted Input. For routing, URLPattern is a global since Node 24 but still Stability 1 – Experimental.