# Compatibilidade APIs Node.js - Url

O módulo `url` no Node.js fornece utilitários para resolução e análise de URLs. Ele permite que desenvolvedores trabalhem com URLs de forma estruturada, facilitando a manipulação e extração de informações delas. Este módulo é essencial para aplicações web que precisam lidar com URLs para roteamento, requisições de API e mais. Está disponível no Azion Runtime através da compatibilidade com Node.js, permitindo que functions analisem a URL da requisição recebida, leiam parâmetros de consulta e construam novas URLs ao fazer proxy ou redirecionar tráfego.

---

## Exemplo: Análise e formatação de URL

O exemplo abaixo mostra como usar o módulo `url` em uma 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;
```

---

## Exemplo: Análise de URL de requisição

Analise URLs de requisições recebidas para extrair informações de roteamento:

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

---

## Exemplo: Construindo URLs dinamicamente

Construa URLs para chamadas de API e redirecionamentos:

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

---

## Exemplo: Manipulação de URLSearchParams

Trabalhe com parâmetros de query string de forma eficiente:

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

---

## Exemplo: Validação e normalização de URL

Valide e normalize URLs fornecidas pelo usuário:

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

---

## Exemplo: Roteamento de URL e correspondência de caminhos

Use análise de URL para roteamento de requisições:

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

---

## APIs com suporte

| API | Status |
|-----|--------|
| `URL` (global) | 🟢 Com suporte |
| `URLSearchParams` | 🟢 Com suporte |
| `url.URL` | 🟢 Com suporte |
| `url.URLSearchParams` | 🟢 Com suporte |
| `url.format()` | 🟢 Com suporte |
| `url.parse()` | 🟡 Depreciado (use `new URL()`) |
| `url.resolve()` | 🟡 Depreciado (use `new URL(relative, base)`) |
| `url.domainToASCII()` | 🟡 Parcialmente suportado |
| `url.domainToUnicode()` | 🟡 Parcialmente suportado |
| `url.fileURLToPath()` | 🟡 Parcialmente suportado |
| `url.pathToFileURL()` | 🟡 Parcialmente suportado |
| `url.urlToHttpOptions()` | 🟡 Parcialmente suportado |

:::note
APIs marcadas como 🟡 Parcialmente suportado têm funcionalidade limitada em comparação com a implementação completa do Node.js. Note que `url.parse()`, `url.resolve()`, e `url.format()` estão depreciados desde Node.js v11.0.0 — prefira as APIs WHATWG `URL` e `URLSearchParams`, que estão disponíveis globalmente sem precisar de import.
:::

---

## Recursos relacionados

- [Documentação do `url` no Node.js](https://nodejs.org/api/url.html)
- [Documentação da API URL no MDN](https://developer.mozilla.org/en-US/docs/Web/API/URL)