Chapter 27: Node.js URL Module
1. What is the url module and why do you need it?
The node:url module is a built-in core module that helps you parse, format, resolve, and manipulate URLs in a safe, standard-compliant way.
Why not just use string methods or regex?
URLs look simple, but they have many tricky rules:
- Different protocols (http, https, file, data, ws, mailto…)
- Authentication (http://user:pass@host.com)
- Ports (:8080)
- Path segments with /, ../, //, encoded characters
- Query strings (?page=2&sort=desc)
- Fragments (#section-2)
- Internationalized domain names (IDN)
- Edge cases that break naive string splitting
Doing this manually is very error-prone → you will eventually write bugs that only appear on certain URLs or browsers.
The url module solves all of this — it follows the WHATWG URL Standard (same standard browsers use).
Modern rule (2025–2026):
Always use node:url when you need to:
- Parse an incoming URL
- Build or modify a URL
- Resolve relative URLs
- Extract hostname, pathname, search params, etc.
2. Modern import style
|
0 1 2 3 4 5 6 7 8 9 |
import { URL, URLSearchParams } from 'node:url'; // Recommended prefix in 2025–2026 import url from 'node:url'; // whole module |
3. The two most important classes
| Class | Purpose | When you use it most often |
|---|---|---|
| URL | Represents a complete URL (parse, format, resolve) | Almost every time you work with URLs |
| URLSearchParams | Handles the query string part (?key=value&…) | Reading or building query parameters |
4. The URL class – your main tool
4.1 Basic parsing
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
const url = new URL('https://aman:secret123@example.com:8443/path/to/resource?sort=desc&page=2&limit=50#section-3'); console.log(url.href); // full normalized URL console.log(url.protocol); // 'https:' console.log(url.username); // 'aman' console.log(url.password); // 'secret123' (be careful logging this!) console.log(url.hostname); // 'example.com' console.log(url.port); // '8443' console.log(url.pathname); // '/path/to/resource' console.log(url.search); // '?sort=desc&page=2&limit=50' console.log(url.hash); // '#section-3' console.log(url.origin); // 'https://example.com:8443' |
4.2 Very common real-world example – parsing incoming request URL
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import { createServer } from 'node:http'; import { URL } from 'node:url'; const server = createServer((req, res) => { const url = new URL(req.url, `http://{req.headers.host}`); console.log('Protocol:', url.protocol); console.log('Host:', url.host); console.log('Pathname:', url.pathname); console.log('Search:', url.search); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ requestedPath: url.pathname, queryParams: Object.fromEntries(url.searchParams) })); }); server.listen(3000); |
Try:
|
0 1 2 3 4 5 6 |
GET http://localhost:3000/api/users?page=3&role=admin |
Response:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
{ "requestedPath": "/api/users", "queryParams": { "page": "3", "role": "admin" } } |
Important note: When parsing req.url in http server, you must provide a base (because req.url is relative).
|
0 1 2 3 4 5 6 7 8 |
new URL(req.url, `http://{req.headers.host}`) // or better in HTTPS: new URL(req.url, `${req.headers['x-forwarded-proto'] || 'http'}://{req.headers.host}`) |
4.3 Working with URLSearchParams (query string handling)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
const url = new URL('https://shop.com/products?category=books&sort=price-desc&page=2&inStock=true'); const params = url.searchParams; // Read values console.log(params.get('category')); // 'books' console.log(params.get('page')); // '2' console.log(params.has('inStock')); // true console.log(params.getAll('tag')); // [] (if multiple) // Iterate all parameters for (const [key, value] of params) { console.log(`${key}: {value}`); } // Modify params.set('page', '3'); params.append('tag', 'bestseller'); params.append('tag', 'discount'); params.delete('sort'); console.log(url.toString()); // → https://shop.com/products?category=books&page=3&inStock=true&tag=bestseller&tag=discount |
Very common pattern – clean query builder
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
function buildApiUrl(base, paramsObj) { const url = new URL(base); for (const [key, value] of Object.entries(paramsObj)) { if (value !== undefined && value !== null) { url.searchParams.set(key, String(value)); } } return url.toString(); } const apiUrl = buildApiUrl('https://api.example.com/v1/tasks', { status: 'pending', limit: 20, sort: 'createdAt-desc', include: 'assignee' }); console.log(apiUrl); // → https://api.example.com/v1/tasks?status=pending&limit=20&sort=createdAt-desc&include=assignee |
5. Resolving relative URLs (very powerful)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
const base = new URL('https://example.com/blog/posts/'); console.log(new URL('page-2', base).href); // → https://example.com/blog/posts/page-2 console.log(new URL('../about', base).href); // → https://example.com/blog/about console.log(new URL('/contact', base).href); // → https://example.com/contact console.log(new URL('https://cdn.com/script.js', base).href); // → https://cdn.com/script.js (absolute URL overrides base) |
Very common pattern – building API endpoints
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
const API_BASE = new URL('https://api.myapp.com/v2/'); function getUserUrl(id) { return new URL(`users/${id}`, API_BASE).toString(); } console.log(getUserUrl(12345)); // → https://api.myapp.com/v2/users/12345 |
6. Common mistakes & how to avoid them
| Mistake | Consequence | Correct way |
|---|---|---|
| new URL(req.url) without base | TypeError: Invalid URL | Always new URL(req.url, base) |
| Logging url.password in production | Passwords leak in logs | Never log url.password or url.username |
| Using + to build URLs | Wrong escaping, broken paths | Always use new URL() + searchParams |
| Forgetting to call .toString() or .href | Get URL object instead of string | Use url.toString() when you need the full URL |
| Assuming search is parsed | url.search is just string | Use url.searchParams for easy get/set/delete |
Summary – Quick decision guide 2025–2026
| You want to… | Use this | Typical real-world use case |
|---|---|---|
| Parse incoming request URL | new URL(req.url, base) | Almost every HTTP/Express/Fastify route |
| Read or modify query parameters | url.searchParams | Pagination, filters, sorting |
| Build a new URL safely | new URL(path, base) | API links, redirect URLs, external service calls |
| Resolve relative paths / URLs | new URL(relative, base) | HTML <base>, link rewriting, sitemap generation |
| Get full normalized URL string | url.toString() or url.href | Logging, redirects, database storage |
Would you like to go much deeper into any specific area?
- URLSearchParams tricks (sorting, bulk operations, encoding)
- Building robust API URL helpers with validation
- Handling redirects and relative URLs correctly
- Parsing complex URLs (auth, IPv6, data: URLs, file: URLs)
- Complete example – API client with URL building & query params
Just tell me which direction feels most useful right now — I’ll continue with detailed, production-ready examples. 😊
