Working with URLs in JavaScript
Written by Joe Attardi✏️
URLs are a critical piece of any web app. If your app makes requests to an API, it’s important to craft the correct URLs for these requests. The URL
API, supported in all modern browsers, provides a way to parse and manipulate URLs. It provides easy access to the various parts of the URL.
Understanding the parts of a URL
Consider the following URL:
https://example.com/api/search?query=foo&sort=asc#results
This URL is made up of the following components:
- Protocol:
https
- Host:
example.com
- Path name:
/api/search
- Query string:
?query=foo&sort=asc
- Hash:
#results
With modern JavaScript, we can parse URLs and extract these different parts as needed.
Parsing URLs
In older browsers, before the URL
API was available, one way that developers parsed URLs was by using an element. This element provides some basic URL parsing. For example, here is a way you could extract the query string from a URL:
function getQueryString(url) {
const link = document.createElement('a');
link.href = url;
return url.search;
}
However, this approach has some disadvantages:
- It requires a DOM environment, which means it wouldn’t work in an environment such as Node.js
- It also has no error handling — if an invalid URL is passed to the
href
attribute, no error is thrown
You could also use a regular expression to parse out the various parts of the URL, but this is tedious and error-prone.
Using the URL
API to parse URLs is straightforward. Just pass the URL you want to parse to the URL
constructor. If the URL string is valid, you’ll get a URL
object back with properties for various parts of the URL:
const url = new URL('https://example.com/api/search?query=foobar');
console.log(url.host); // example.com
console.log(url.pathname); // /api/search
console.log(url.search); // ?query=foobar
Parsing the query string
You can access the query string of a URL
in two ways:
- The
search
property, which is a string containing the full query string (including the?
character) - The
searchParams
property, which is aURLSearchParams
object
If you’re interested in the value of a particular parameter in the query string, you can use its get
method to get the parameter by its name:
const url = new URL('https://example.com/api/search?query=foobar&maxResults=10');
console.log(url.searchParams.get('query'); // foobar
console.log(url.searchParams.get('maxResults'); // 10
If there are multiple parameters with the same name, you can use getAll
to get an array containing all of the values for that name:
const url = new URL('https://example.com/api/search?tag=tag1&tag=tag2&tag=tag3');
console.log(url.searchParams.getAll('tag')); // ['tag1', 'tag2', 'tag3']
Building query strings
Building a query string by hand can be tricky, especially if any query parameters contain special characters that need to be escaped. For example, if a query parameter needs to contain a &
character, you’d need to encode it as %26
. To cover these situations, you need to use the encodeURIComponent
function:
let queryString = 'foo=bar';
queryString += '&baz=qux';
queryString += '&tag=' + encodeURIComponent('one&two');
console.log(queryString); // foo=bar&baz=qux&tag=one%26two
You can build the query string more safely by using the URLSearchParams
object:
const params = new URLSearchParams();
params.append('foo', 'bar');
params.append('baz', 'qux');
params.append('tag', 'one&two');
console.log(params.toString()); // foo=bar&baz=qux&tag=one%26two
Advantages of using URLSearchParams
include:
- You don’t have to worry about the
&
characters separating the parameters - You don’t need to URI-encode the parameter values
- You don’t need to use string concatenation
Iterating over query parameters
Without a URLSearchParams
object, it’s a little tricky to iterate over the parameters in a query string. You would need to split strings several times — first into groups of key/value pairs, then again to split up the key and value:
function listQueryParams(queryString) {
queryString.split('&').forEach(param => {
const [key, value] = param.split('=');
console.log(`${key}: ${value}`);
});
}
If the parameters could contain encoded characters, you’d also need to decode them:
function listQueryParams(queryString) {
queryString.split('&').forEach(param => {
const [key, value] = param.split('=');
console.log(`${key}: ${decodeURIComponent(value)}`);
});
}
Instead, you can use URLSearchParams
‘s entries
method to iterate over the key/value pairs:
function listQueryParams(queryString) {
const params = new URLSearchParams(queryString);
params.entries().forEach(([key, value]) => console.log(`${key}: ${value}`));
}
Building a full URL
Here’s a full example of building a URL with a base URL and some query parameters:
const url = new URL('https://example.com/api/search');
url.searchParams.append('query', 'test');
url.searchParams.append('tag', 'tag1');
url.searchParams.append('tag', 'tag2');
// https://example.com/api/search?query=test&tag=tag1&tag=tag2
console.log(url.toString());
Checking for valid URLs
You might try using a regular expression to validate a URL, but it’s notoriously difficult to craft a regular expression that fully captures a valid URL string.
Instead, you can reach for the URL
API. The URL
constructor will throw an error if you give it an invalid URL. You can use this to check if a URL is valid:
function isValidURL(url) {
try {
new URL(url);
return true;
} catch (error) {
return false;
}
}
With newer browsers, this is even easier. There’s a newer URL.canParse
static method that performs similar validation with a single line of code. Like the isValidURL
function above, it takes a potential URL string and returns true
or false
depending on the validity of the URL string.
Creating relative URLs
The URL
API has a powerful mechanism for resolving relative URLs. Normally, the argument to the URL
constructor will throw an error if it’s not a full, valid URL. However, you can specify a second argument that serves as a base from which to build a relative URL. If you use the two-argument approach, the first argument doesn’t have to be a valid URL but the second one does.
Let’s look at a simple case first:
new URL('/about', 'https://example.com').href;
The URL
constructor takes the base URL of https://example.com
and adds the relative path /about
, resulting in https://example.com/about
.
What about this one:
new URL('profile', 'https://example.com/users').href;
You might expect this to be https://example.com/users/profile
, but it actually comes out to https://example.com/profile
. This behaves just like a relative link; it takes the parent path segment, which is the root of example.com
, and then adds profile
.
Let’s look at one more example of using a relative URL. You can also use ..
to go back up the path hierarchy:
new URL('../profile', 'https://example.com/users/123').href;
This one comes out to https://example.com/profile
. Remember that relative URLs start at the parent path segment. Then, this one has ..
in it, which goes up one more path segment.
If you call the URL
constructor with a relative URL and specify an invalid or incomplete URL for the base URL, you’ll get an error. You’ll also get an error if you use a relative URL without a full base URL:
new URL('../profile', '/about'); // error!
new URL('../profile'); // error
Working with the window.location
object
You might be familiar with the window.location
object, which represents the current page’s URL. This object also has properties such as href
and pathname
, so you might think it’s a URL
object. This is a different object, a Location
, which has some properties in common with URL
, but is also missing some (such as the searchParams
property).
Even though it’s not a URL
object, you can still use window.location
to construct new URL
objects. You can pass window.location
to the URL
constructor to create a new full-blown URL
with searchParams
and all, based on the current URL, or you can even use it as the base URL when constructing relative URLs:
new URL('/profile', window.location).href;
Matching patterns within a URL using URLPattern
Using a URL
makes it easy to get the path from a URL. For example, in the URL https://example.com/api/users/123/profile
, the path name is /api/users/123/profile
. What if we wanted to get just the user ID, 123
, from this URL?
As we discussed earlier, it can be difficult to create proper regular expressions to validate and extract parts of a URL.
It’s not available in all browsers just yet, but you can use the URLPattern
API to match and extract parts of the URL, matching patterns you specify. This can be particularly useful for things like client-side routing in a single-page application (SPA).
Using the user profile URL as an example, let’s create a URLPattern
to get the user ID. We can use a leading :
character to denote a named placeholder, which can be used later to match that part of the URL:
const pattern = new URLPattern('https://example.com/api/users/:userId/profile');
const matcher = pattern.exec('https://example.com/api/users/123/profile');
console.log(matcher.pathname.groups.userId); // 123
When you call exec
on a URLPattern
, it needs a valid URL. It returns a matcher object that contains properties for each of the URL’s parts (protocol
, host
, pathname
, etc.). Each of these properties also has a groups
property, which maps placeholder names like :userId
to their values within the URL.
If you’re only concerned about matching one part of the URL, such as the path name as we’ve done here, you can specify wildcards in the URL pattern as well. Or, instead of a URL string, you can pass an object containing the parts of the URL you’re interested in matching:
new URLPattern('https://*/api/users/:userId/profile');
new URLPattern({ pathname: '/api/users/:userId/profile' });
The URLPattern
API is still not available in all browsers. At the time of writing, it’s not yet supported in Firefox or Safari. You can see the latest browser support information at CanIUse.com.
Summary
The URL
API is a versatile interface for constructing, validating, and manipulating URLs in JavaScript. It’s safer and less error-prone to use than manual parsing or regular expressions. By using a URLSearchParams
object, you can build a query string without worrying about string concatenation or encoding special characters.
The URLPattern
API takes this a step further, supporting wildcards and named placeholders, so you can slice and dice URLs to meet your app’s needs! Further reading:
Get set up with LogRocket’s modern error tracking in minutes:
- Visit https://logrocket.com/signup/ to get an app ID.
- Install LogRocket via NPM or script tag.
LogRocket.init()
must be called client-side, not server-side.
NPM:
$ npm i --save logrocket
// Code:
import LogRocket from 'logrocket';
LogRocket.init('app/id');
Script Tag:
Add to your HTML:
<script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script>
<script>window.LogRocket && window.LogRocket.init('app/id');</script>
3.(Optional) Install plugins for deeper integrations with your stack:
- Redux middleware
- ngrx middleware
- Vuex plugin