Node.js and Express course menu
Node.js path and URL Modules: Working with File Paths and URLs
Build file paths safely with path.join and path.resolve, take paths apart with basename, extname and dirname, use import.meta.dirname for files next to your code, understand Windows and POSIX separators, and parse URLs and query strings with the URL class.
On this page
File paths look different on each operating system (C:\shop\data\orders.json on Windows, /home/asha/shop/data/orders.json on macOS and Linux), so building them by gluing strings together breaks easily. The path module builds and takes apart paths correctly for the system your code runs on.
Building paths#
import path from "node:path";
console.log(path.join("data", "orders", "2026.json"));
console.log(path.join("data", "orders", "..", "users.json")); // ".." goes up a level
console.log(path.sep);data\orders\2026.json
data\users.json
\path.join inserts the right separator and tidies up .. and repeated separators. path.resolve produces an absolute path, starting from the current working directory unless you give an absolute part:
import path from "node:path";
console.log(path.isAbsolute(path.resolve("data", "orders.json")));
console.log(path.resolve("/srv/app", "public", "index.html"));true
C:\srv\app\public\index.htmlOn Windows, a path starting with / means the root of the current drive, which is why C: appears; on macOS and Linux the result is /srv/app/public/index.html.
Taking paths apart#
import path from "node:path";
const file = path.join("uploads", "invoices", "invoice-2026-10.pdf");
console.log(path.basename(file)); // file name
console.log(path.basename(file, ".pdf")); // without the extension
console.log(path.extname(file)); // extension
console.log(path.dirname(file)); // folder
console.log(path.parse(file).name);invoice-2026-10.pdf
invoice-2026-10
.pdf
uploads\invoices
invoice-2026-10path.extname is handy for choosing a content type or rejecting uploads with unexpected extensions.
Files next to your code#
Relative paths in readFile are resolved from the current working directory (where you ran node), which may not be the script's folder. To read a file that sits beside the script, build the path from import.meta.dirname:
import path from "node:path";
const configPath = path.join(import.meta.dirname, "config.json");
console.log(path.basename(path.dirname(configPath)), "/", path.basename(configPath));
console.log("Working directory is the script folder?", process.cwd() === import.meta.dirname);node src/config.jssrc / config.json
Working directory is the script folder? falseThe script lives in src/, but node was started from the project folder, so the two differ. Building paths from import.meta.dirname works no matter where node is started. (In CommonJS, the equivalent is __dirname.)
Cross-platform paths#
- Always use
path.join/path.resolveinstead of"data/" + nameor hard-coded\. - In URLs, imports and
package.jsonscripts, use/: they are not file-system paths. path.posixandpath.win32force one style when you need it, for example to build URL-like paths on Windows:
import path from "node:path";
console.log(path.posix.join("images", "products", "lamp.jpg"));
console.log(path.win32.join("images", "products", "lamp.jpg"));images/products/lamp.jpg
images\products\lamp.jpgURLs#
The global URL class parses web addresses into their parts:
const url = new URL("https://shop.example.com:8080/products/search?q=lamp&page=2#reviews");
console.log(url.protocol, url.hostname, url.port);
console.log(url.pathname);
console.log(url.searchParams.get("q"), url.searchParams.get("page"));
console.log(url.hash);https: shop.example.com 8080
/products/search
lamp 2
#reviewssearchParams reads and builds query strings, taking care of encoding special characters:
const url = new URL("https://shop.example.com/search");
url.searchParams.set("q", "steel bottle & cup");
url.searchParams.set("sort", "price");
url.searchParams.append("tag", "eco");
url.searchParams.append("tag", "kitchen");
console.log(url.href);
console.log(url.searchParams.getAll("tag"));
console.log(Object.fromEntries(new URLSearchParams("a=1&b=2")));https://shop.example.com/search?q=steel+bottle+%26+cup&sort=price&tag=eco&tag=kitchen
[ 'eco', 'kitchen' ]
{ a: '1', b: '2' }Values from searchParams are always strings (or null if missing): convert numbers with Number(...).
Common mistakes#
Practice#
Practice exercise
Given the uploaded file names ["photo.JPG", "report.pdf", "notes.txt", "selfie.png"], print only the images (.jpg, .jpeg, .png, in any letter case) with their names lower-cased, and the URL https://cdn.example.com/uploads/<name> for each.
Show hint
Check path.extname(name).toLowerCase() against a Set of allowed extensions, and build the URL with new URL("uploads/" + name, "https://cdn.example.com/").
Show solution
import path from "node:path";
const images = new Set([".jpg", ".jpeg", ".png"]);
for (const name of ["photo.JPG", "report.pdf", "notes.txt", "selfie.png"]) {
if (!images.has(path.extname(name).toLowerCase())) continue;
const lower = name.toLowerCase();
console.log(lower, new URL(`uploads/${lower}`, "https://cdn.example.com/").href);
}photo.jpg https://cdn.example.com/uploads/photo.jpg
selfie.png https://cdn.example.com/uploads/selfie.pngKey takeaway#
Finished this lesson?