デバッグ6 分で読める

Node.js の ERR_INVALID_URL を修正する方法

Node.js の ERR_INVALID_URL エラーは、URL コンストラクタや url.parse() が有効な URL ではない文字列を受け取ったときに発生します。よくある原因としては、プロトコルの欠落、エンコードされていない特殊文字、不正な percent-encoding などが挙げられます。パースする前に入力を検証し、特殊文字をエンコードすることで修正できます。

ERR_INVALID_URL の原因は何か?

ERR_INVALID_URL エラー(正式には TypeError [ERR_INVALID_URL]: Invalid URL)は、URL コンストラクタや従来の url.parse() 関数が、有効な URL としてパースできない文字列を受け取ったときに Node.js がスローします。このエラーは、ユーザー入力を処理する場合や、設定ファイルをパースする場合、外部ソースから取得した URL を扱う場合によく発生します。

URL コンストラクタは WHATWG URL Standard に準拠しており、これは古い RFC ベースのパースよりも厳格です。有効な URL として受け入れられるには、文字列に有効なスキーム(https: など)、有効なオーソリティ(ホスト名)、そして正しく整形されたパスとクエリのコンポーネントが含まれている必要があります。

// これは ERR_INVALID_URL をスローする
try {
  const url = new URL('not-a-url');
} catch (err) {
  console.log(err.code);    // 'ERR_INVALID_URL'
  console.log(err.message); // 'Invalid URL: not-a-url'
  console.log(err.input);   // 'not-a-url'
}

よくある原因と修正方法

原因 1: プロトコル/スキームの欠落。 URL コンストラクタは https://http:// のようなスキームを必要とします。example.com/pathwww.example.com のような文字列は失敗します。

// 失敗: プロトコルがない
new URL('example.com/path');  // ERR_INVALID_URL

// 修正: プロトコルを追加する
new URL('https://example.com/path');  // 動作する!

// 修正: プロトコルがなければ追加する
function ensureProtocol(urlString) {
  if (!/^https?:\/\//i.test(urlString)) {
    return 'https://' + urlString;
  }
  return urlString;
}
new URL(ensureProtocol('example.com/path'));  // 動作する!

原因 2: エンコードされていない特殊文字。 スペース、波括弧、パイプ、特定の Unicode 文字などは、エンコードせずに URL に含めることはできません。

// 失敗: エンコードされていないスペースや特殊文字
new URL('https://example.com/my file.pdf');    // ERR_INVALID_URL
new URL('https://example.com/path?q=a b');     // 一部のバージョンでは失敗することがある

// 修正: 問題のある部分をエンコードする
const filename = encodeURIComponent('my file.pdf');
new URL('https://example.com/' + filename);    // 動作する!

// 修正: スペースを含む完全な URL には encodeURI を使う
const rawUrl = 'https://example.com/my file.pdf';
new URL(encodeURI(rawUrl));  // 動作する!

原因 3: 不正な percent-encoding。 URL にパーセント記号が含まれていて、その後に 16 進数の 2 桁がちょうど続いていない場合、パーサーはそれを拒否します。

// 失敗: 不正なパーセントエンコーディング
new URL('https://example.com/100%done');       // ERR_INVALID_URL
new URL('https://example.com/path?q=50%');     // ERR_INVALID_URL

// 修正: 単独のパーセント記号を %25 としてエンコードする
function fixPercentSigns(urlString) {
  return urlString.replace(/%(?![0-9A-Fa-f]{2})/g, '%25');
}
new URL(fixPercentSigns('https://example.com/100%done'));
// 動作する! URL は https://example.com/100%25done になる

原因 4: 空または null の入力。 空文字列、null、undefined を URL コンストラクタに渡した場合も、このエラーが発生します。

// 失敗: 空または null の入力
new URL('');           // ERR_INVALID_URL
new URL(null);         // ERR_INVALID_URL
new URL(undefined);    // ERR_INVALID_URL

// 修正: パースする前に入力を検証する
function parseUrl(input) {
  if (!input || typeof input !== 'string') {
    return null;
  }
  try {
    return new URL(input);
  } catch {
    return null;
  }
}

原因 5: ベースのない相対 URL。 URL コンストラクタは、デフォルトで第 1 引数を絶対 URL として扱います。/path/to/page のような相対 URL には、第 2 引数としてベース URL が必要です。

// 失敗: ベースのない相対 URL
new URL('/api/users');  // ERR_INVALID_URL

// 修正: ベース URL を指定する
new URL('/api/users', 'https://example.com');
// 動作する! → https://example.com/api/users

// API クライアントに便利なパターン
const BASE_URL = 'https://api.example.com';
const endpoint = new URL('/v2/users?page=1', BASE_URL);
console.log(endpoint.href);
// "https://api.example.com/v2/users?page=1"

パースする前に URL を検証する方法

Node.js で URL を検証する最も信頼できる方法は、try-catch ブロックの中で URL コンストラクタを使うことです。組み込みの URL.isValid() メソッドは(Node.js 22 時点では)存在しないため、エラーをキャッチするのが標準的なアプローチです。

// シンプルな URL バリデータ
function isValidUrl(string) {
  try {
    new URL(string);
    return true;
  } catch {
    return false;
  }
}

console.log(isValidUrl('https://example.com'));  // true
console.log(isValidUrl('not-a-url'));            // false
console.log(isValidUrl(''));                     // false

// 許可するプロトコルを指定するバリデータ
function isValidHttpUrl(string) {
  try {
    const url = new URL(string);
    return url.protocol === 'http:' || url.protocol === 'https:';
  } catch {
    return false;
  }
}

console.log(isValidHttpUrl('https://example.com'));     // true
console.log(isValidHttpUrl('ftp://example.com'));       // false
console.log(isValidHttpUrl('javascript:alert(1)'));     // false

安全な URL パースのパターン

以下は、よくあるすべてのエラーケースを処理し、パース済みの URL オブジェクトか意味のあるエラーを返す、堅牢な URL パースパターンです。

class UrlParser {
  static parse(input, base) {
    // 入力を検証する
    if (!input || typeof input !== 'string') {
      return { ok: false, error: 'Input must be a non-empty string' };
    }

    // 空白を除去する
    const trimmed = input.trim();

    // よくある問題を修正する
    let urlString = trimmed;

    // プロトコルがなければ追加する
    if (/^[a-zA-Z0-9]/.test(urlString) && !urlString.includes('://')) {
      urlString = 'https://' + urlString;
    }

    // 単独のパーセント記号を修正する
    urlString = urlString.replace(/%(?![0-9A-Fa-f]{2})/g, '%25');

    try {
      const url = base ? new URL(urlString, base) : new URL(urlString);

      // 任意: 安全なプロトコルに制限する
      if (!['http:', 'https:'].includes(url.protocol)) {
        return { ok: false, error: 'Unsupported protocol: ' + url.protocol };
      }

      return { ok: true, url };
    } catch (err) {
      return { ok: false, error: err.message };
    }
  }
}

// 使い方
const result = UrlParser.parse('example.com/path?q=hello world');
if (result.ok) {
  console.log(result.url.href);
} else {
  console.error(result.error);
}

予防のためのベストプラクティス

以下のプラクティスに従うことで、Node.js アプリケーションで ERR_INVALID_URL エラーを回避し、より堅牢な URL 処理を構築できます。

  • URL のパースは常に try-catch で囲む。 特にユーザー入力、環境変数、データベース、外部 API から取得した URL 文字列については、決して有効だと決めつけないでください。
  • 文字列連結ではなく URL API を使う。 文字列を連結するのではなく、URL コンストラクタと URLSearchParams を使って URL を組み立てましょう。この API はエンコードを自動的に処理します。
  • 起動時に環境変数を検証する。 アプリケーションが環境変数や設定ファイルから URL を読み込む場合は、初めて使うときではなく、アプリケーションの起動時に検証してください。
  • ユーザー入力は URL に埋め込む前にエンコードする。 クエリパラメータの値には常に encodeURIComponent() を、ユーザーが指定した完全な URL には encodeURI() を使いましょう。
  • 複雑なケースには URL ビルダーを使う。 動的な部分が多い URL を組み立てる場合は、エンコードを一貫して処理するヘルパー関数やクラスを作成してください。
  • エラー時には元の入力をログに残す。 ERR_INVALID_URL エラーをキャッチしたら、問題の原因となった入力を(機密データを含む可能性がある場合はサニタイズしたうえで)ログに記録し、デバッグに役立てましょう。
// 良いプラクティス: 起動時に設定 URL を検証する
const requiredUrls = ['API_BASE_URL', 'AUTH_SERVER_URL', 'WEBHOOK_URL'];

for (const envVar of requiredUrls) {
  const value = process.env[envVar];
  if (!value) {
    throw new Error(envVar + ' environment variable is required');
  }
  try {
    new URL(value);
  } catch {
    throw new Error(envVar + ' is not a valid URL: ' + value);
  }
}

// 良いプラクティス: URL の組み立てには URL API を使う
function buildApiUrl(endpoint, params) {
  const url = new URL(endpoint, process.env.API_BASE_URL);
  for (const [key, value] of Object.entries(params)) {
    url.searchParams.set(key, String(value));
  }
  return url.toString();
}

const searchUrl = buildApiUrl('/api/search', {
  q: 'Node.js & Express',
  page: 1
});
// "https://api.example.com/api/search?q=Node.js+%26+Express&page=1"

関連記事

無料ツールを試す