# Node.js API compatibility - Url

The `url` module in Node.js provides utilities for URL resolution and parsing. It allows developers to work with URLs in a structured way, making it easier to manipulate and extract information from them. This module is essential for web applications that need to handle URLs for routing, API requests, and more. It's available in the Azion Runtime through Node.js compatibility, so functions can parse the incoming request URL, read query parameters, and build new URLs when proxying or redirecting traffic.

---

## Example: URL parsing and formatting

The example below shows how to use the `url` module in a function:

```javascript
/**
 * An example of using Node.js URL API in an Azion Function.
 * Support:
 * - Partially supported (Extended by library `url`)
 * @module runtime-apis/nodejs/url/main
 * @example
 * // Execute with Azion Bundler:
 * npx edge-functions build
 * npx edge-functions dev
 */
import url from "node:url";
/**
 * An example of using the Node.js URL API in an Azion Function.
 * @param {*} event
 * @returns {Promise<Response>}
 */
const main = async (event) => {
  /**
   * URL globalThis object
   * https://developer.mozilla.org/en-US/docs/Web/API/URL
   * if use URL in the browser, don't need to import
   */
  const newUrl = new URL("https://example.com/some/path?format=json&page=1");
  console.log("newURL", newUrl);

  const urlFormated = url.format({
    protocol: "https",
    hostname: "example.com",
    pathname: "/some/path",
    query: {
      page: 1,
      format: "json",
    },
  });

  console.log(url.parse(urlFormated));

  return new Response("Done!", { status: 200 });
};

export default main;
```

---

## Example: Request URL parsing

Parse incoming request URLs to extract routing information:

```javascript
// url module imported for legacy API demonstration
// new URL() and URLSearchParams are available globally
import url from "node:url";

const main = async (event) => {
  const request = event.request;
  const requestUrl = new URL(request.url);

  // Extract URL components
  const urlInfo = {
    href: requestUrl.href,
    origin: requestUrl.origin,
    protocol: requestUrl.protocol,
    hostname: requestUrl.hostname,
    port: requestUrl.port,
    pathname: requestUrl.pathname,
    search: requestUrl.search,
    hash: requestUrl.hash
  };

  // Parse query parameters
  const searchParams = requestUrl.searchParams;
  const queryParams = {};
  for (const [key, value] of searchParams) {
    queryParams[key] = value;
  }

  // ⚠️ url.parse() is deprecated since Node.js v11.0.0
  // Prefer the WHATWG URL API (new URL()) shown above
  const parsedUrl = url.parse(request.url, true);
  console.log("Parsed URL:", parsedUrl);

  // Get path segments
  const segments = requestUrl.pathname.split("/").filter(Boolean);

  return new Response(JSON.stringify({
    urlInfo,
    queryParams,
    segments,
    parsedQuery: parsedUrl.query
  }), {
    headers: { "Content-Type": "application/json" }
  });
};

export default main;
```

---

## Example: Building URLs dynamically

Construct URLs for API calls and redirects:

```javascript
import url from "node:url";

const main = async (event) => {
  // Build API URL with query parameters
  const apiUrl = new URL("https://api.example.com/v1/users");
  apiUrl.searchParams.set("page", "1");
  apiUrl.searchParams.set("limit", "10");
  apiUrl.searchParams.set("sort", "name");
  apiUrl.searchParams.set("active", "true");

  console.log("API URL:", apiUrl.toString());

  // Build redirect URL
  const redirectUrl = url.format({
    protocol: "https",
    hostname: "app.example.com",
    pathname: "/dashboard",
    query: {
      ref: "login",
      session: "abc123"
    }
  });

  // Resolve relative URL
  // ⚠️ url.resolve() is deprecated since Node.js v11.0.0
  // Prefer: new URL(relativePath, baseUrl).href
  const baseUrl = "https://example.com/docs/";
  const relativePath = "../api/reference";
  const resolvedUrl = url.resolve(baseUrl, relativePath);
  console.log("Resolved URL (deprecated):", resolvedUrl);

  // Modern alternative using URL constructor
  const modernResolvedUrl = new URL(relativePath, baseUrl).href;
  console.log("Resolved URL (modern):", modernResolvedUrl);

  // Build URL from parts
  const parts = {
    protocol: "https:",
    hostname: "cdn.example.com",
    port: "443",
    pathname: "/assets/images/logo.png"
  };
  const fullUrl = url.format(parts);

  return new Response(JSON.stringify({
    apiUrl: apiUrl.toString(),
    redirectUrl,
    resolvedUrl,
    builtUrl: fullUrl
  }), {
    headers: { "Content-Type": "application/json" }
  });
};

export default main;
```

---

## Example: URLSearchParams manipulation

Work with query string parameters efficiently:

```javascript
import url from "node:url";

const main = async (event) => {
  const requestUrl = new URL(event.request.url);
  const params = requestUrl.searchParams;

  // Read parameters
  const page = params.get("page") || "1";
  const limit = params.get("limit") || "10";
  const sort = params.get("sort");

  console.log("Page:", page, "Limit:", limit, "Sort:", sort);

  // Check if parameter exists
  const hasFilter = params.has("filter");
  console.log("Has filter:", hasFilter);

  // Get all values for a parameter (multi-value)
  const tags = params.getAll("tag");
  console.log("Tags:", tags);

  // Modify parameters
  params.set("page", "2");
  params.set("timestamp", Date.now().toString());
  params.delete("debug");

  // Iterate over parameters
  const allParams = [];
  for (const [key, value] of params) {
    allParams.push({ key, value });
  }

  // Sort parameters alphabetically
  params.sort();

  // Convert to string
  const queryString = params.toString();

  // Create new URLSearchParams from object
  const newParams = new URLSearchParams({
    format: "json",
    version: "2",
    pretty: "true"
  });

  return new Response(JSON.stringify({
    originalParams: { page, limit, sort },
    hasFilter,
    tags,
    modifiedParams: allParams,
    queryString,
    newParamsString: newParams.toString()
  }), {
    headers: { "Content-Type": "application/json" }
  });
};

export default main;
```

---

## Example: URL validation and normalization

Validate and normalize user-provided URLs:

```javascript
import url from "node:url";

const main = async (event) => {
  const request = event.request;
  // Note: In a real application, you might receive URLs from the request body
  // const { urls: customUrls } = await request.json().catch(() => ({}));

  // URLs to validate
  const testUrls = [
    "https://example.com/path",
    "http://localhost:3000/api?query=test",
    "HTTPS://EXAMPLE.COM/UPPERCASE",
    "https://example.com:443/standard-port",
    "https://user:pass@example.com/auth",
    "not-a-url",
    "ftp://files.example.com"
  ];

  const results = testUrls.map(testUrl => {
    try {
      const parsed = new URL(testUrl);
      return {
        original: testUrl,
        valid: true,
        normalized: parsed.href,
        protocol: parsed.protocol,
        hostname: parsed.hostname,
        isHttps: parsed.protocol === "https:"
      };
    } catch (error) {
      return {
        original: testUrl,
        valid: false,
        error: error.message
      };
    }
  });

  // Normalize a URL (lowercase hostname, default port removal)
  const normalizeUrl = (urlString) => {
    try {
      const parsed = new URL(urlString);
      parsed.hostname = parsed.hostname.toLowerCase();
      
      // Remove default ports
      if (parsed.protocol === "https:" && parsed.port === "443") {
        parsed.port = "";
      }
      if (parsed.protocol === "http:" && parsed.port === "80") {
        parsed.port = "";
      }
      
      return parsed.href;
    } catch {
      return null;
    }
  };

  const normalizedUrls = testUrls.map(normalizeUrl);

  return new Response(JSON.stringify({
    validationResults: results,
    normalizedUrls
  }), {
    headers: { "Content-Type": "application/json" }
  });
};

export default main;
```

---

## Example: URL routing and path matching

Use URL parsing for request routing:

```javascript
import url from "node:url";

const main = async (event) => {
  const requestUrl = new URL(event.request.url);
  const pathname = requestUrl.pathname;

  // Define routes
  const routes = {
    "/": { handler: "home", methods: ["GET"] },
    "/api/users": { handler: "users", methods: ["GET", "POST"] },
    "/api/users/:id": { handler: "userDetail", methods: ["GET", "PUT", "DELETE"] },
    "/api/products": { handler: "products", methods: ["GET"] },
    "/health": { handler: "health", methods: ["GET"] }
  };

  // Match route
  const matchRoute = (path) => {
    // Exact match
    if (routes[path]) {
      return { ...routes[path], params: {} };
    }

    // Pattern match for :id style routes
    for (const [pattern, config] of Object.entries(routes)) {
      if (pattern.includes(":")) {
        const regex = new RegExp("^" + pattern.replace(/:\w+/g, "([^/]+)") + "$");
        const match = path.match(regex);
        if (match) {
          const paramNames = pattern.match(/:\w+/g) || [];
          const params = {};
          paramNames.forEach((name, i) => {
            params[name.slice(1)] = match[i + 1];
          });
          return { ...config, params };
        }
      }
    }

    return null;
  };

  const matchedRoute = matchRoute(pathname);
  const method = event.request.method;

  // Check if method is allowed
  const methodAllowed = matchedRoute?.methods?.includes(method);

  // Build response
  const response = {
    pathname,
    method,
    matched: matchedRoute ? {
      handler: matchedRoute.handler,
      params: matchedRoute.params,
      methodAllowed
    } : null,
    query: Object.fromEntries(requestUrl.searchParams)
  };

  return new Response(JSON.stringify(response, null, 2), {
    status: matchedRoute && methodAllowed ? 200 : matchedRoute ? 405 : 404,
    headers: { "Content-Type": "application/json" }
  });
};

export default main;
```

---

## Supported APIs

| API | Status |
|-----|--------|
| `URL` (global) | 🟢 Supported |
| `URLSearchParams` | 🟢 Supported |
| `url.URL` | 🟢 Supported |
| `url.URLSearchParams` | 🟢 Supported |
| `url.format()` | 🟢 Supported |
| `url.parse()` | 🟡 Deprecated (use `new URL()`) |
| `url.resolve()` | 🟡 Deprecated (use `new URL(relative, base)`) |
| `url.domainToASCII()` | 🟡 Partially supported |
| `url.domainToUnicode()` | 🟡 Partially supported |
| `url.fileURLToPath()` | 🟡 Partially supported |
| `url.pathToFileURL()` | 🟡 Partially supported |
| `url.urlToHttpOptions()` | 🟡 Partially supported |

:::note
APIs marked as 🟡 Partially supported have limited functionality compared to the full Node.js implementation. Note that `url.parse()`, `url.resolve()`, and `url.format()` are deprecated since Node.js v11.0.0 — prefer the WHATWG `URL` and `URLSearchParams` APIs, which are globally available without requiring an import.
:::

---

## Related resources

- [Node.js `url` documentation](https://nodejs.org/api/url.html)
- [MDN URL API documentation](https://developer.mozilla.org/en-US/docs/Web/API/URL)