# node:url

The `node:url` module provides the Node.js utilities for resolving, parsing, and formatting URLs. With it, a function reads the parts of a URL in a structured way, changes them, and builds URLs for routing and API requests. Azion Runtime provides the module through its Node.js compatibility. A function can use it to parse the incoming request URL, read its query parameters, and build URLs when it proxies or redirects traffic. In Azion Runtime, `url.URL` is the same object as the global `URL`, and the legacy functions `url.parse()`, `url.format()`, and `url.resolve()` work in a deployed function.

---

## Examples

Each example is a complete function that imports `url` from `node:url`. The response below each example is the one a deployed function returns.

### URL parsing and formatting

This function creates a URL with the global `URL` class, builds a URL string from an object with `url.format()`, and parses that string with `url.parse()`:

```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
 * // Build and run with the Azion CLI:
 * azion build
 * azion 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;
```

The function responds with `Done!`. It logs the URL, the deprecation warning that `url.parse()` prints, and the parsed object:

```text
newURL "https://example.com/some/path?format=json&page=1"
[DeprecationWarning] [unenv] [node:url] DEP0169: `url.parse()` behavior is not standardized and prone to errors that have security implications. Use the WHATWG URL API instead. CVEs are not issued for `url.parse()` vulnerabilities.
{"auth":null,"hash":null,"host":"example.com","hostname":"example.com","href":"https://example.com/some/path?page=1&format=json","path":"/some/path?page=1&format=json","pathname":"/some/path","protocol":"https:","search":"?page=1&format=json","slashes":true,"port":null,"query":"page=1&format=json"}
```

### Request URL parsing

This function reads the components of the request URL, collects its query parameters, and splits its path into segments:

```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;
```

For a `GET` request to `https://example.com/blog/post-1`, the function responds with the URL components, an empty query, and the two path segments:

```json
{"urlInfo":{"href":"https://example.com/blog/post-1","origin":"https://example.com","protocol":"https:","hostname":"example.com","port":"","pathname":"/blog/post-1","search":"","hash":""},"queryParams":{},"segments":["blog","post-1"],"parsedQuery":{}}
```

### Dynamic URL construction

This function builds an API URL with query parameters, a redirect URL with `url.format()`, and a URL from its parts. It also resolves a relative path twice, with `url.resolve()` and with the `URL` constructor:

```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;
```

The function responds with the four URLs. Both ways of resolving the relative path log `https://example.com/api/reference`, and the URL built from parts keeps the port `443`:

```json
{"apiUrl":"https://api.example.com/v1/users?page=1&limit=10&sort=name&active=true","redirectUrl":"https://app.example.com/dashboard?ref=login&session=abc123","resolvedUrl":"https://example.com/api/reference","builtUrl":"https://cdn.example.com:443/assets/images/logo.png"}
```

### Query parameter handling

This function reads, checks, changes, and sorts the query parameters of the request, then builds a second query string from an object:

```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;
```

For a request without a query string, the function responds with the default values, the changed parameters, and both query strings. The `timestamp` value comes from `Date.now()`:

```json
{"originalParams":{"page":"1","limit":"10","sort":null},"hasFilter":false,"tags":[],"modifiedParams":[{"key":"page","value":"2"},{"key":"timestamp","value":"1767268800000"}],"queryString":"page=2&timestamp=1767268800000","newParamsString":"format=json&version=2&pretty=true"}
```

### URL validation and normalization

This function checks a list of URLs with the `URL` constructor, then normalizes each valid URL: it lowercases the hostname and removes a default port:

```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;
```

The function responds with the result for each URL and the normalized list. The `URL` constructor rejects `not-a-url` with the message `Invalid URL: 'not-a-url'`, and the normalized list holds `null` in its place:

```json
{"validationResults":[{"original":"https://example.com/path","valid":true,"normalized":"https://example.com/path","protocol":"https:","hostname":"example.com","isHttps":true},{"original":"http://localhost:3000/api?query=test","valid":true,"normalized":"http://localhost:3000/api?query=test","protocol":"http:","hostname":"localhost","isHttps":false},{"original":"HTTPS://EXAMPLE.COM/UPPERCASE","valid":true,"normalized":"https://example.com/UPPERCASE","protocol":"https:","hostname":"example.com","isHttps":true},{"original":"https://example.com:443/standard-port","valid":true,"normalized":"https://example.com/standard-port","protocol":"https:","hostname":"example.com","isHttps":true},{"original":"https://user:pass@example.com/auth","valid":true,"normalized":"https://user:pass@example.com/auth","protocol":"https:","hostname":"example.com","isHttps":true},{"original":"not-a-url","valid":false,"error":"Invalid URL: 'not-a-url'"},{"original":"ftp://files.example.com","valid":true,"normalized":"ftp://files.example.com/","protocol":"ftp:","hostname":"files.example.com","isHttps":false}],"normalizedUrls":["https://example.com/path","http://localhost:3000/api?query=test","https://example.com/UPPERCASE","https://example.com/standard-port","https://user:pass@example.com/auth",null,"ftp://files.example.com/"]}
```

### URL routing and path matching

This function matches the request path against a route table, including a route with an `:id` parameter, and checks whether the route accepts the request method. It answers `404` when no route matches, and `405` when a route matches but its methods do not include the request method:

```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;
```

For a `GET` request to `https://example.com/blog/post-1`, a path the route table does not list, the function takes its no-match branch and responds with status `404` and this body:

```json
{
  "pathname": "/blog/post-1",
  "method": "GET",
  "matched": null,
  "query": {}
}
```

---

## Supported APIs

The table lists the status of each `node:url` API in Azion Runtime:

| API                      | Status                                                   |
| ------------------------ | -------------------------------------------------------- |
| `URL` (global)           | 🟢 Supported                                             |
| `URLSearchParams`        | 🟢 Supported                                             |
| `url.URL`                | 🟢 Supported                                             |
| `url.URLSearchParams`    | 🟢 Supported                                             |
| `url.format()`           | 🟢 Supported                                             |
| `url.parse()`            | 🟡 Supported (deprecated, use `new URL()`)               |
| `url.resolve()`          | 🟡 Supported (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                                   |

APIs marked 🟡 Partially supported have limited functionality compared to the full Node.js implementation. In a deployed function, `url.fileURLToPath("file:///tmp/x.txt")` returns `/tmp/x.txt`, `url.pathToFileURL("/tmp/x.txt")` returns `file:///tmp/x.txt`, and `url.domainToASCII("español.com")` returns `xn--espaol-zwa.com`.

A function that calls `url.parse()` can log this deprecation warning, as the first example shows:

```text
[DeprecationWarning] [unenv] [node:url] DEP0169: `url.parse()` behavior is not standardized and prone to errors that have security implications. Use the WHATWG URL API instead. CVEs are not issued for `url.parse()` vulnerabilities.
```

Node.js deprecates `url.parse()`, `url.resolve()`, and `url.format()` from v11.0.0. Use the WHATWG `URL` and `URLSearchParams` APIs instead: in Azion Runtime, both are globals that need no import.

---

## Related resources

- [Node.js APIs](/en/documentation/devtools/runtime/node.md): Every Node.js module Azion Runtime resolves, and the status of `url` among them.
- [Use Node.js APIs through polyfills](/en/documentation/guides/application-development/functions-and-runtime/use-polyfills.md): How the build resolves `node:url` and the other Node.js imports for Azion Runtime.
- [Node.js url documentation](https://nodejs.org/api/url.html): The Node.js reference for each `node:url` API the table lists.
- [MDN URL API documentation](https://developer.mozilla.org/en-US/docs/Web/API/URL): The Web reference for the global `URL` class that every example uses.
