URL API

The URL API provides utilities for parsing, constructing, normalizing, and encoding URLs. It implements the WHATWG URL Standard for consistent URL handling.

Overview

Andromeda's URL implementation provides a robust way to work with URLs, supporting both absolute and relative URL parsing, validation, and manipulation.

URL Constructor

Syntax

new URL(url: string, base?: string)

Creates a new URL object from a URL string and an optional base URL.

Parameters:

Returns: A new URL object

Throws: TypeError if the URL is invalid

Examples

Absolute URLs

// Create URL from absolute URL
const url1 = new URL("https://example.com/path?query=value#fragment");
console.log(url1.toString()); // "https://example.com/path?query=value#fragment"

Relative URLs with Base

const baseUrl = "https://developer.mozilla.org";

// Relative path
const url1 = new URL("/docs", baseUrl);
console.log(url1.toString()); // "https://developer.mozilla.org/docs"

// Relative path with existing path
const url2 = new URL("en-US/docs", baseUrl);
console.log(url2.toString()); // "https://developer.mozilla.org/en-US/docs"

// Absolute path overwrites base path
const url3 = new URL("/en-US/docs", "https://developer.mozilla.org/fr-FR/");
console.log(url3.toString()); // "https://developer.mozilla.org/en-US/docs"

Using URL Objects as Base

const baseUrl = new URL("https://developer.mozilla.org/");
const docsUrl = new URL("en-US/docs", baseUrl);
console.log(docsUrl.toString()); // "https://developer.mozilla.org/en-US/docs"

Static Methods

URL.parse(url: string, base?: string)

A static method that safely parses a URL without throwing an exception.

// Valid URL
const result1 = URL.parse("https://example.com/path");
console.log(result1); // URL object

// Valid relative URL with base
const result2 = URL.parse("docs", "https://example.com/");
console.log(result2); // URL object

// Invalid URL returns null (in standard implementation)
const result3 = URL.parse("not-a-valid-url");
console.log(result3); // null or throws depending on implementation

Instance Methods

toString()

Returns the complete URL as a string.

const url = new URL("https://example.com/path?query=value");
console.log(url.toString()); // "https://example.com/path?query=value"

// toString() is called automatically in string contexts
console.log(`URL: ${url}`); // "URL: https://example.com/path?query=value"

Usage Examples

Basic URL Parsing

// Parse a complete URL
const apiUrl = new URL("https://api.example.com/v1/users?limit=10&offset=0");
console.log(apiUrl.toString());

// Parse relative URL
const baseUrl = "https://api.example.com/";
const endpoint = new URL("v1/users", baseUrl);
console.log(endpoint.toString()); // "https://api.example.com/v1/users"

Building URLs Dynamically

const base = "https://api.example.com";

// Build different endpoints
const usersEndpoint = new URL("/v1/users", base);
const postsEndpoint = new URL("/v1/posts", base);
const profileEndpoint = new URL("/v1/profile", base);

console.log(usersEndpoint.toString()); // "https://api.example.com/v1/users"
console.log(postsEndpoint.toString()); // "https://api.example.com/v1/posts"
console.log(profileEndpoint.toString()); // "https://api.example.com/v1/profile"

Working with Relative Paths

const docBase = new URL("https://docs.example.com/guide/");

// Navigate to different sections
const introUrl = new URL("introduction", docBase);
const setupUrl = new URL("setup", docBase);
const advancedUrl = new URL("../advanced/", docBase);

console.log(introUrl.toString()); // "https://docs.example.com/guide/introduction"
console.log(setupUrl.toString()); // "https://docs.example.com/guide/setup"
console.log(advancedUrl.toString()); // "https://docs.example.com/advanced/"

URL Validation

function isValidUrl(urlString: string, base?: string): boolean {
  try {
    new URL(urlString, base);
    return true;
  } catch {
    return false;
  }
}

console.log(isValidUrl("https://example.com")); // true
console.log(isValidUrl("not-a-url")); // false
console.log(isValidUrl("/path", "https://example.com")); // true
console.log(isValidUrl("/path")); // false (no base for relative URL)

Safe URL Parsing

function parseUrlSafely(urlString: string, base?: string): URL | null {
  if ("parse" in URL) {
    // Use static parse method if available
    return URL.parse(urlString, base);
  } else {
    // Fallback to constructor with try-catch
    try {
      return new URL(urlString, base);
    } catch {
      return null;
    }
  }
}

const url = parseUrlSafely("https://example.com/path");
if (url) {
  console.log("Valid URL:", url.toString());
} else {
  console.log("Invalid URL");
}

URL Resolution Examples

Path Resolution

const base = "https://example.com/docs/guide/";

// Different path types
console.log(new URL("intro.html", base)); // https://example.com/docs/guide/intro.html
console.log(new URL("./intro.html", base)); // https://example.com/docs/guide/intro.html
console.log(new URL("../api/", base)); // https://example.com/docs/api/
console.log(new URL("/help", base)); // https://example.com/help
console.log(new URL("?search=term", base)); // https://example.com/docs/guide/?search=term
console.log(new URL("#section", base)); // https://example.com/docs/guide/#section

Protocol and Domain Handling

const base = "https://example.com/path";

// Different protocols
console.log(new URL("http://other.com", base)); // http://other.com
console.log(new URL("//other.com/path", base)); // https://other.com/path
console.log(new URL("ftp://files.com", base)); // ftp://files.com

Browser Compatibility

Andromeda's URL implementation follows the WHATWG URL Standard and provides:

Best Practices

Use Absolute Base URLs

Always provide absolute URLs as base when working with relative URLs:

// Good
const goodBase = "https://api.example.com/";
const endpoint = new URL("users", goodBase);

// Avoid - relative base URLs can be confusing
const relativeBase = "/api/";

Validate User Input

Always validate URLs from user input:

function createApiUrl(userPath: string): URL | null {
  try {
    // Validate and sanitize user input
    const baseUrl = "https://api.example.com/";
    return new URL(userPath, baseUrl);
  } catch (error) {
    console.error("Invalid URL path:", userPath);
    return null;
  }
}

Use URL for Path Building

Prefer URL constructor over string concatenation:

// Good
const apiBase = "https://api.example.com/";
const userUrl = new URL(`users/${userId}`, apiBase);

// Less reliable
const userUrlString = `https://api.example.com/users/${userId}`;

Common Patterns

API Endpoint Builder

class ApiClient {
  private baseUrl: string;

  constructor(baseUrl: string) {
    this.baseUrl = baseUrl;
  }

  buildUrl(path: string): URL {
    return new URL(path, this.baseUrl);
  }

  getUserUrl(userId: string): URL {
    return this.buildUrl(`users/${userId}`);
  }

  getPostsUrl(userId: string): URL {
    return this.buildUrl(`users/${userId}/posts`);
  }
}

const client = new ApiClient("https://api.example.com/v1/");
console.log(client.getUserUrl("123").toString());
// "https://api.example.com/v1/users/123"

URL Path Navigation

function navigateUrl(currentUrl: URL, relativePath: string): URL {
  return new URL(relativePath, currentUrl);
}

const currentPage = new URL("https://docs.example.com/guide/setup.html");
const nextPage = navigateUrl(currentPage, "configuration.html");
const prevSection = navigateUrl(currentPage, "../introduction/");

console.log(nextPage.toString()); // "https://docs.example.com/guide/configuration.html"
console.log(prevSection.toString()); // "https://docs.example.com/introduction/"

Error Handling

URL constructor throws TypeError for invalid URLs:

try {
  const url = new URL("not a valid url");
} catch (error) {
  if (error instanceof TypeError) {
    console.error("Invalid URL:", error.message);
  }
}

// Common invalid URL patterns
const invalidUrls = [
  "not-a-url",
  "http://",
  "://example.com",
  "relative/path", // without base
];

invalidUrls.forEach((urlString) => {
  try {
    new URL(urlString);
    console.log(`${urlString} is valid`);
  } catch {
    console.log(`${urlString} is invalid`);
  }
});

Limitations

Current implementation notes: