A URL contains several independent pieces: a scheme, a host, a path, a query, and sometimes a fragment. Treating the entire address as a string encourages mistakes such as appending a second question mark or assuming that a host-shaped substring identifies the destination. Node 24 provides the standard URL and URLSearchParams classes globally. These examples use those classes without an additional package. The goal is to keep structural parsing separate from application policy. A parser can tell you which host a redirect names, but your application must decide whether that host is allowed.
Resolve relative addresses with a deliberate base
The URL constructor accepts an absolute address or a relative reference accompanied by a base. A relative path without a base throws because there is no origin to resolve it against. The shape of the base path matters: a base ending in a slash represents a directory, while a base without that slash can have its final segment replaced. A leading slash in the reference resolves from the origin root. A reference beginning with two slashes supplies a new host while retaining the base scheme.
For an incoming HTTP request, req.url is normally a relative request target. A fixed configured origin is a useful parsing base. Do not assume the request Host header is a trusted statement of your application's public origin; proxies and deployment settings influence it, and clients may supply unexpected values. Use pathname to route and searchParams to inspect the query. A fragment belongs to the client-side address and is not part of a normal HTTP request target.
Encode values once and preserve repeated keys
Set parameter values as ordinary strings and let URLSearchParams encode them. A literal plus sign and a space are different input characters. In query serialization, spaces commonly become plus signs, while a literal plus is escaped as percent followed by 2B. Manually calling encodeURIComponent before searchParams.set produces double encoding because the percent signs are encoded again. That error may remain hidden with alphabetic input and appear only when a learner tests spaces, ampersands, or non-English text.
append adds another pair with the same name. set replaces the existing values for that name. get returns the first value; getAll returns every value in order. Repeated values are useful for tag filters but dangerous when different layers disagree about which duplicate wins. Define a policy for fields such as page, redirect, or accountId. Reject duplicates for singular fields rather than silently allowing a validation layer to inspect one value while downstream code consumes another.
Convert query values under an application contract
Every query value starts as text. Number conversion alone is not sufficient validation: an empty string becomes zero, and permissive parsing functions can accept only the initial numeric portion of a malformed value. For a positive page number, first require the desired decimal shape, then convert, then check the range and safe integer property. Treat an absent field differently from an explicitly empty one when the product behavior requires that distinction.
Avoid constructing a plain object as though it preserved the entire query. Object.fromEntries loses repeated keys because object properties have one final value. Use getAll for multi-value filters and dedicated validation for singular fields. Set an upper bound on the number of tags and the length of individual values before using them in database queries or logs. Sorting parameters can produce a stable serialization for a cache key, but it does not make arbitrary query input trusted or guarantee that another service signs URLs with the same normalization rules.
Validate redirect destinations after parsing
A string prefix test is a poor origin check. A deceptive hostname may begin with the expected characters, and a URL can place a familiar name in its username component before a different host. Parse first, compare the resulting origin against an exact configured allowlist, and restrict the accepted protocol. Origin includes scheme and effective port; a different port is a different origin. Username and password components deserve an explicit rejection in redirects even when a destination otherwise looks familiar.
Relative redirects can be useful because they stay within the configured application origin after resolution. A network-path reference can still change that origin, so the same validation applies to relative-looking strings. Parsing a destination also does not prevent server-side request forgery if the server later fetches it. Server fetches need separate address, DNS, and network access policy. The worked redirect helper demonstrates a small browser redirect policy only. It intentionally does not promise that an allowlisted URL is safe for a privileged backend to download.
Node.js documentation and version notes
Node.js URL and URLSearchParams Examples
Each example shows the code, the output it produces and what each part does.
Parsing repeated query keys and relative URLs
Save this fixture as url-demo.mjs and run it with Node 24. The tag filter deliberately contains repeated keys, while the search term contains both a space and a literal plus sign. The printed serialization shows that they survive distinctly. The two relative references illustrate how the same base handles a sibling path and an origin-root path. The final validation keeps page as a singular positive integer. It rejects duplicates before conversion, so the rule does not depend on which value get would otherwise return. The output contains no external request; parsing a URL never downloads it.
const url = new URL('/catalog?tag=red&tag=blue', 'https://shop.example.test/app/');
url.searchParams.set('q', 'tea + cake');
console.log(url.pathname);
console.log(JSON.stringify(url.searchParams.getAll('tag')));
console.log(url.search);
console.log(new URL('orders', 'https://shop.example.test/app/').pathname);
console.log(new URL('/orders', 'https://shop.example.test/app/').pathname);
function pageOf(params) {
const values = params.getAll('page');
if (values.length === 0) return 1;
if (values.length !== 1 || !/^[1-9]\d*$/.test(values[0])) throw new Error('Invalid page');
const page = Number(values[0]);
if (!Number.isSafeInteger(page) || page > 1000) throw new Error('Invalid page');
return page;
}
console.log(pageOf(new URLSearchParams('page=3')));
try { pageOf(new URLSearchParams('page=3&page=4')); }
catch (error) { console.log(error.message); }/catalog
["red","blue"]
?tag=red&tag=blue&q=tea+%2B+cake
/app/orders
/orders
3
Invalid pageNode.js URL and URLSearchParams in Real Projects
Write a same-origin redirect validator
Write a redirect helper that accepts application-relative destinations and absolute addresses on the configured HTTPS origin. Reject a destination on another origin and reject embedded credentials. Return the normalized pathname and query rather than forwarding the unexamined input. The solution resolves each candidate against a fixed base and applies the same checks regardless of whether the original text looked relative. Its two successful cases include a query parameter, proving that normalization preserves navigation state. The failure case uses a network-path reference, which is a common oversight in policies that merely reject strings beginning with http.
const base = new URL('https://shop.example.test/');
function redirectTarget(input) {
const target = new URL(input, base);
if (target.origin !== base.origin || target.protocol !== 'https:' ||
target.username || target.password) throw new Error('Redirect rejected');
return target.pathname + target.search;
}
console.log(redirectTarget('/orders?page=2'));
console.log(redirectTarget('https://shop.example.test/account'));
try { redirectTarget('//other.example.test/orders'); }
catch (error) { console.log(error.message); }/orders?page=2
/account
Redirect rejectedNode.js URL and URLSearchParams FAQ
Why did a plus sign turn into a space?
A raw query string uses form-style parameter encoding, where a plus sign represents a space. If the intended value contains a literal plus, construct the parameter through set or append so it becomes percent-encoded. This is particularly relevant for tokens or base64 values that contain plus characters. Do not concatenate an unescaped value into a raw query and then expect the parser to infer which plus signs were literal.
Can I use Object.fromEntries for all parameters?
It is convenient only after deciding that each relevant name is singular. Repeated pairs collapse into one property, losing information needed to validate duplicates or preserve multiple selected filters. Read getAll first for names where multiplicity matters. For a page parameter, reject more than one value. For a tag parameter, retain the array and validate each member. The query's data shape should follow the API contract, not an incidental conversion shortcut.
Does URL parsing stop malicious redirects?
Parsing exposes the real destination components but does not apply your security policy. Compare the parsed origin with an exact trusted origin and reject unwanted schemes and credentials. A host-looking substring in the path or username is irrelevant to that comparison. For navigation within one application, returning a validated path and query is often clearer than returning the original absolute string. Invalid constructor input must also be handled as a rejected request.
Should I decode a query before constructing URL?
No. Decode through the structured API after it separates components. Decoding the entire address first can turn an encoded ampersand or slash into syntax and change the structure you intended to parse. Likewise, pre-encoding a parameter before set leads to double escaping. Keep plain input values at the parameter API boundary and serialized URLs at the transport boundary. If a downstream service expects a different canonicalization, document that separate contract explicitly.
Node.js URL and URLSearchParams Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
In the fixture, how many values does getAll("tag") return after set("q", ...)?
Node.js URL and URLSearchParams Quiz
2 questions to check what you learned.
Why reject duplicate page parameters before Number conversion?
- Different layers might otherwise use different occurrences
- Number conversion removes all duplicate keys automatically
- URLSearchParams rejects repeated names during parsing
Which check identifies the complete trusted redirect origin?
- Compare parsed target.origin with the configured origin
- Check whether the original string starts with the trusted hostname
- Search for the trusted hostname anywhere in pathname