JavaScript URL API Complete Guide

Master URL parsing, URLSearchParams, URL construction, and encoding techniques

JavaScript URL API Complete Guide

The URL API is the modern way to handle URLs in JavaScript. This article covers various uses of URL and URLSearchParams.

URL Object

Creating URLs

// Full URL
const url = new URL('https://example.com:8080/path/to/page?query=value#section');

// Relative URL + base URL
const relative = new URL('/api/users', 'https://example.com');
// https://example.com/api/users

// Parse failure throws error
try {
  new URL('invalid-url');  // TypeError
} catch (e) {
  console.log('Invalid URL');
}

// Validate URL
function isValidURL(string) {
  try {
    new URL(string);
    return true;
  } catch {
    return false;
  }
}

URL Properties

const url = new URL('https://user:pass@example.com:8080/path/page?q=1&b=2#hash');

// Protocol
url.protocol;    // 'https:'

// Host information
url.hostname;    // 'example.com'
url.port;        // '8080'
url.host;        // 'example.com:8080'

// Authentication
url.username;    // 'user'
url.password;    // 'pass'

// Path
url.pathname;    // '/path/page'

// Query string
url.search;      // '?q=1&b=2'

// Hash
url.hash;        // '#hash'

// Full origin
url.origin;      // 'https://example.com:8080'

// Full URL
url.href;        // Full URL string
url.toString();  // Same as above

Modifying URLs

const url = new URL('https://example.com/page');

// Modify parts
url.pathname = '/new-path';
url.search = '?key=value';
url.hash = '#section';

console.log(url.href);
// 'https://example.com/new-path?key=value#section'

// Modify host
url.hostname = 'api.example.com';
url.port = '3000';

// Modify protocol
url.protocol = 'http:';

// All modifications are auto-normalized
url.pathname = 'no-leading-slash';
url.pathname;  // '/no-leading-slash'

URLSearchParams

Creating and Parsing

// From string
const params1 = new URLSearchParams('name=Alice&age=25');

// From object
const params2 = new URLSearchParams({
  name: 'Alice',
  age: 25
});

// From array (supports duplicate keys)
const params3 = new URLSearchParams([
  ['tag', 'javascript'],
  ['tag', 'web'],
  ['tag', 'frontend']
]);

// From URL
const url = new URL('https://example.com?key=value');
const params4 = url.searchParams;

// From form data
const form = document.querySelector('form');
const params5 = new URLSearchParams(new FormData(form));

Reading Parameters

const params = new URLSearchParams('name=Alice&age=25&tags=a&tags=b');

// Get single value
params.get('name');     // 'Alice'
params.get('missing');  // null

// Get all values
params.getAll('tags');  // ['a', 'b']

// Check existence
params.has('name');     // true
params.has('missing');  // false

// Get parameter count
params.size;  // 4 (ES2023)

// Convert to string
params.toString();  // 'name=Alice&age=25&tags=a&tags=b'

Modifying Parameters

const params = new URLSearchParams();

// Set parameter (overwrites existing)
params.set('name', 'Alice');
params.set('age', '25');

// Append parameter (allows duplicates)
params.append('tag', 'javascript');
params.append('tag', 'web');

// Delete parameter
params.delete('age');

// Delete parameter with specific value (ES2023)
params.delete('tag', 'web');

// Sort parameters
params.sort();

console.log(params.toString());
// 'name=Alice&tag=javascript'

Iterating Parameters

const params = new URLSearchParams('a=1&b=2&c=3');

// for...of iteration
for (const [key, value] of params) {
  console.log(`${key}: ${value}`);
}

// entries()
for (const [key, value] of params.entries()) {
  console.log(key, value);
}

// keys()
for (const key of params.keys()) {
  console.log(key);
}

// values()
for (const value of params.values()) {
  console.log(value);
}

// forEach
params.forEach((value, key) => {
  console.log(key, value);
});

// Convert to object
const obj = Object.fromEntries(params);
// { a: '1', b: '2', c: '3' }

// Note: duplicate keys keep only last value
const multi = new URLSearchParams('tag=a&tag=b');
Object.fromEntries(multi);  // { tag: 'b' }

URL Encoding

Automatic Encoding

// URL object handles encoding automatically
const url = new URL('https://example.com/path');

// Special characters in path
url.pathname = '/files/document';
url.pathname;  // '/files/document'

// Query parameters auto-encode
url.searchParams.set('query', 'hello world');
url.search;  // '?query=hello+world'

// Auto-decode when reading
url.searchParams.get('query');  // 'hello world'

Manual Encoding Functions

// encodeURIComponent - encode component
encodeURIComponent('hello world');  // 'hello%20world'
encodeURIComponent('key=value&foo=bar');  // 'key%3Dvalue%26foo%3Dbar'
encodeURIComponent('中文');  // '%E4%B8%AD%E6%96%87'

// decodeURIComponent - decode component
decodeURIComponent('hello%20world');  // 'hello world'
decodeURIComponent('%E4%B8%AD%E6%96%87');  // '中文'

// encodeURI - encode full URI (preserves URL special chars)
encodeURI('https://example.com/path?query=hello world');
// 'https://example.com/path?query=hello%20world'

// decodeURI - decode full URI
decodeURI('https://example.com/path?query=hello%20world');
// 'https://example.com/path?query=hello world'

// Difference
const text = 'a=1&b=2';
encodeURIComponent(text);  // 'a%3D1%26b%3D2' (encodes = and &)
encodeURI(text);           // 'a=1&b=2' (preserves = and &)

Base64 Encoding

// String to Base64
function stringToBase64(str) {
  // Handle Unicode
  const bytes = new TextEncoder().encode(str);
  const binString = Array.from(bytes, byte =>
    String.fromCharCode(byte)
  ).join('');
  return btoa(binString);
}

// Base64 to string
function base64ToString(base64) {
  const binString = atob(base64);
  const bytes = Uint8Array.from(binString, char =>
    char.charCodeAt(0)
  );
  return new TextDecoder().decode(bytes);
}

// Usage
const encoded = stringToBase64('Hello World');
const decoded = base64ToString(encoded);

// URL-safe Base64
function toBase64URL(base64) {
  return base64
    .replace(/\+/g, '-')
    .replace(/\//g, '_')
    .replace(/=+$/, '');
}

function fromBase64URL(base64url) {
  let base64 = base64url
    .replace(/-/g, '+')
    .replace(/_/g, '/');
  while (base64.length % 4) {
    base64 += '=';
  }
  return base64;
}

Practical Applications

Building API URLs

function buildAPIUrl(endpoint, params = {}) {
  const url = new URL(endpoint, 'https://api.example.com');

  // Add query parameters
  Object.entries(params).forEach(([key, value]) => {
    if (value !== undefined && value !== null) {
      if (Array.isArray(value)) {
        value.forEach(v => url.searchParams.append(key, v));
      } else {
        url.searchParams.set(key, value);
      }
    }
  });

  return url.toString();
}

// Usage
buildAPIUrl('/users', { page: 1, limit: 10 });
// 'https://api.example.com/users?page=1&limit=10'

buildAPIUrl('/search', { q: 'keyword', tags: ['a', 'b'] });
// 'https://api.example.com/search?q=keyword&tags=a&tags=b'

Parsing Current URL

// Get current page URL parameters
function getQueryParams() {
  const params = new URLSearchParams(window.location.search);
  return Object.fromEntries(params);
}

// Get specific parameter
function getQueryParam(name) {
  const params = new URLSearchParams(window.location.search);
  return params.get(name);
}

// Update URL parameters (without page reload)
function updateQueryParams(updates) {
  const url = new URL(window.location.href);

  Object.entries(updates).forEach(([key, value]) => {
    if (value === null || value === undefined) {
      url.searchParams.delete(key);
    } else {
      url.searchParams.set(key, value);
    }
  });

  window.history.replaceState({}, '', url.toString());
}

// Usage
updateQueryParams({ page: 2, filter: 'active' });

URL Templates

// URL template replacement
function resolveURLTemplate(template, params) {
  let result = template;

  // Replace path parameters
  Object.entries(params).forEach(([key, value]) => {
    result = result.replace(`:${key}`, encodeURIComponent(value));
  });

  return result;
}

// Usage
resolveURLTemplate('/users/:id/posts/:postId', {
  id: 123,
  postId: 456
});
// '/users/123/posts/456'

// Version with query parameters
function buildURL(template, pathParams = {}, queryParams = {}) {
  let path = resolveURLTemplate(template, pathParams);
  const url = new URL(path, 'https://api.example.com');

  Object.entries(queryParams).forEach(([key, value]) => {
    if (value !== undefined) {
      url.searchParams.set(key, value);
    }
  });

  return url.toString();
}
// Parse hash route
function parseHashRoute(hash) {
  // Format: #/path?query
  const hashUrl = new URL(hash.slice(1), 'http://dummy');
  return {
    path: hashUrl.pathname,
    params: Object.fromEntries(hashUrl.searchParams)
  };
}

// Usage
parseHashRoute('#/users?page=2&sort=name');
// { path: '/users', params: { page: '2', sort: 'name' } }

// Parse complex deep links
function parseDeepLink(url) {
  const parsed = new URL(url);
  return {
    scheme: parsed.protocol.replace(':', ''),
    host: parsed.hostname,
    path: parsed.pathname,
    query: Object.fromEntries(parsed.searchParams),
    fragment: parsed.hash.slice(1)
  };
}

URL Validation and Normalization

// Validate URL format
function validateURL(input, options = {}) {
  const { allowedProtocols = ['http:', 'https:'] } = options;

  try {
    const url = new URL(input);

    if (!allowedProtocols.includes(url.protocol)) {
      return { valid: false, error: 'Protocol not allowed' };
    }

    return { valid: true, url };
  } catch {
    return { valid: false, error: 'Invalid URL format' };
  }
}

// Normalize URL
function normalizeURL(input) {
  const url = new URL(input);

  // Remove default ports
  if ((url.protocol === 'https:' && url.port === '443') ||
      (url.protocol === 'http:' && url.port === '80')) {
    url.port = '';
  }

  // Remove trailing slashes
  url.pathname = url.pathname.replace(/\/+$/, '') || '/';

  // Sort query parameters
  url.searchParams.sort();

  // Remove empty hash
  if (url.hash === '#') {
    url.hash = '';
  }

  return url.toString();
}

// Compare two URLs
function isSameURL(url1, url2) {
  return normalizeURL(url1) === normalizeURL(url2);
}

Safe URL Handling

// Check if external link
function isExternalLink(url, baseHost = window.location.host) {
  try {
    const parsed = new URL(url, window.location.href);
    return parsed.host !== baseHost;
  } catch {
    return false;
  }
}

// Extract domain
function extractDomain(url) {
  try {
    const parsed = new URL(url);
    return parsed.hostname;
  } catch {
    return null;
  }
}

// Safe URL redirect
function safeRedirect(url, allowedHosts = []) {
  try {
    const parsed = new URL(url, window.location.href);

    // Only allow http/https
    if (!['http:', 'https:'].includes(parsed.protocol)) {
      return null;
    }

    // Check host whitelist
    if (allowedHosts.length > 0 && !allowedHosts.includes(parsed.hostname)) {
      return null;
    }

    return parsed.toString();
  } catch {
    return null;
  }
}

URL Shortening

// Simple URL identifier generation
function generateShortId(length = 6) {
  const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  let result = '';
  for (let i = 0; i < length; i++) {
    result += chars.charAt(Math.floor(Math.random() * chars.length));
  }
  return result;
}

// URL mapping (simplified)
class URLShortener {
  constructor(baseUrl) {
    this.baseUrl = baseUrl;
    this.urlMap = new Map();
  }

  shorten(longUrl) {
    // Check if already exists
    for (const [short, long] of this.urlMap) {
      if (long === longUrl) return this.baseUrl + short;
    }

    // Generate new short link
    let shortId;
    do {
      shortId = generateShortId();
    } while (this.urlMap.has(shortId));

    this.urlMap.set(shortId, longUrl);
    return this.baseUrl + shortId;
  }

  expand(shortUrl) {
    const url = new URL(shortUrl);
    const shortId = url.pathname.slice(1);
    return this.urlMap.get(shortId) || null;
  }
}

Best Practices Summary

URL API Best Practices:
┌─────────────────────────────────────────────────────┐
│                                                     │
│   Use URL Object                                    │
│   ├── Avoid manual string concatenation            │
│   ├── Leverage auto-encoding features              │
│   └── Use properties to access parts               │
│                                                     │
│   Use URLSearchParams                               │
│   ├── Parse and build query strings                │
│   ├── Handle duplicates with getAll/append         │
│   └── Convert to object with Object.fromEntries    │
│                                                     │
│   Security                                          │
│   ├── Validate user-input URLs                     │
│   ├── Check protocol and host whitelists           │
│   └── Prevent open redirect vulnerabilities        │
│                                                     │
└─────────────────────────────────────────────────────┘
MethodPurposeExample
new URL()Parse/build URLnew URL(‘/path’, base)
url.searchParamsAccess query paramsurl.searchParams.get()
URLSearchParamsManipulate query stringnew URLSearchParams(str)
encodeURIComponentEncode URL componentencodeURIComponent(value)

Master the URL API to build robust URL handling logic.