Fix TypeError [ERR_INVALID_URL]: Invalid URL in Node.js

beginner๐Ÿ’š Node.js2026-05-11| Node.js 10+ (all platforms: Linux, macOS, Windows) โ€” affects WHATWG URL API, fetch(), and any code using `new URL()`

Error Message

TypeError [ERR_INVALID_URL]: Invalid URL
#nodejs#url#url-api#fetch#url-constructor

TL;DR

You passed a malformed or relative URL to new URL(), fetch(), or another API that demands a fully valid absolute URL. Two-second fix: make sure the URL has a scheme (https:// or http://). For relative paths, pass the base URL as the second argument.

// Broken
const url = new URL('/api/users'); // TypeError [ERR_INVALID_URL]: Invalid URL

// Fixed
const url = new URL('/api/users', 'https://example.com');
console.log(url.href); // https://example.com/api/users

Root Cause

Node.js follows the WHATWG URL standard โ€” the same spec browsers use. That means new URL(input, base?) is strict by design: pass a relative path with no base, and it throws immediately, no questions asked.

These are the situations that reliably trigger the error:

  • Relative path with no base: new URL('/path')
  • URL is undefined, null, or an empty string
  • URL contains spaces or unencoded special characters like # or |
  • Protocol is missing: new URL('example.com/path') โ€” looks valid, isn't
  • Variable interpolation gone wrong โ€” the string ends up being something unexpected
  • Calling fetch('/api/data') in Node.js โ€” unlike browsers, Node has no implicit base URL context

Fix Approaches

1. Relative URL โ€” pass the base as the second argument

This is the most common mistake. Browsers have window.location as an implicit base; Node.js doesn't. You have to supply it explicitly.

// Wrong
const u = new URL('/users/123');

// Right
const u = new URL('/users/123', 'https://api.example.com');
console.log(u.href); // https://api.example.com/users/123

2. URL is undefined or null

Classic env var trap โ€” process.env.API_URL is undefined when the variable isn't set, and new URL(undefined) blows up immediately.

const endpoint = process.env.API_URL; // undefined if not set
new URL(endpoint); // TypeError [ERR_INVALID_URL]: Invalid URL

// Fix: validate before using
if (!endpoint) throw new Error('API_URL env var is not set');
const url = new URL(endpoint);

3. Missing protocol / bare hostname

api.example.com/users looks like a valid URL. It isn't โ€” at least not to the WHATWG parser, which requires an explicit scheme.

// Wrong โ€” no scheme
new URL('api.example.com/users');

// Right
new URL('https://api.example.com/users');

4. Spaces or invalid characters in the URL

Any URL built from user input or dynamic data needs encoding first. URLSearchParams handles this automatically โ€” use it instead of template literals.

const searchTerm = 'hello world';

// Wrong
const u = new URL(`https://example.com/search?q=${searchTerm}`);

// Right โ€” URLSearchParams encodes for you
const u = new URL('https://example.com/search');
u.searchParams.set('q', searchTerm);
console.log(u.href); // https://example.com/search?q=hello+world

5. fetch() with a relative URL in Node.js

Node 18+ ships native fetch(), but there's a catch: unlike the browser, it has no base URL. Relative paths that work fine in React or the browser will fail here. Always pass absolute URLs.

// Wrong in Node.js (works in browser, breaks here)
const res = await fetch('/api/data');

// Right
const BASE = process.env.API_URL ?? 'http://localhost:3000';
const res = await fetch(`${BASE}/api/data`);

6. Wrap with try/catch for user-supplied input

Don't trust external input. Wrap it, catch the error, and fail gracefully instead of crashing the process.

function parseUrl(input) {
  try {
    return new URL(input);
  } catch {
    return null; // or throw a friendlier error
  }
}

const result = parseUrl(userInput);
if (!result) {
  console.error('Invalid URL provided:', userInput);
}

7. Building URLs dynamically โ€” use the URL API properly

String concatenation is fragile โ€” one stray slash or unencoded character and you're back to this error. Let the URL API handle path and query construction instead.

const base = new URL('https://api.example.com');
const endpoint = new URL('/v2/users', base);
endpoint.searchParams.set('page', '2');
endpoint.searchParams.set('limit', '50');

console.log(endpoint.href);
// https://api.example.com/v2/users?page=2&limit=50

Verification

Drop this into a file called check-url.js and run it with node check-url.js. Every line should print OK:. A FAIL: line tells you exactly which case is still broken.

const cases = [
  ['https://example.com/path', undefined],
  ['/relative', 'https://example.com'],
  ['https://example.com/search', undefined],
];

for (const [input, base] of cases) {
  try {
    const u = base ? new URL(input, base) : new URL(input);
    console.log('OK:', u.href);
  } catch (e) {
    console.error('FAIL:', input, '->', e.message);
  }
}

Quick Checklist

  • Does the URL start with http:// or https://?
  • Is the variable actually a string โ€” not undefined or null?
  • For relative paths: is a base URL passed as the second argument?
  • Any unencoded spaces or special characters in the URL?
  • If using fetch() in Node.js: is the URL absolute?

Further Reading

Related Error Notes