Paths, URLs and the os module

Node.js · Essentials · 9 min

String-concatenated paths break on Windows and when scripts run from another folder.

Never glue paths with + "/" +. The node:path module knows the platform's separator and normalises .. and duplicate slashes:

  • path.join("a", "b", "../c.txt") → a/c.txt — joins and normalises segments.
  • path.resolve("data", "x.json") → an absolute path, resolved against the current working directory unless a segment is already absolute.
  • path.dirname, path.basename(p, ".js"), path.extname — take paths apart; path.parse returns all parts as an object.
  • path.relative(from, to) — the path from one location to another.
  • path.sep is / on POSIX and \ on Windows; path.posix and path.win32 force a style (use posix for URL-like paths).

The crucial distinction is where relative paths resolve: against process.cwd(), the directory the user launched Node from. To find a file next to your module regardless of where the process started, anchor on the module itself: path.join(import.meta.dirname, "templates", "email.html").

ES modules identify themselves with a file URL (import.meta.url, e.g. file:///home/me/app/main.js). Most fs functions accept a URL directly, and node:url converts both ways with fileURLToPath and pathToFileURL. new URL("./data.json", import.meta.url) is a neat way to locate a sibling file.

The node:os module answers questions about the machine: os.cpus().length (or better, os.availableParallelism()) for sizing worker pools, os.tmpdir(), os.homedir(), os.EOL, os.totalmem() / os.freemem(), os.platform().

Example

import path from "node:path";
import os from "node:os";
import { fileURLToPath } from "node:url";
import { readFile } from "node:fs/promises";

console.log(path.join("logs", "2025", "..", "app.log")); // logs/app.log
console.log(path.extname("photo.final.jpeg"));           // .jpeg
console.log(path.parse("/srv/app/index.js").name);       // index

// Anchor on this module, not on process.cwd()
const tpl = path.join(import.meta.dirname, "templates", "welcome.txt");
console.log(path.relative(process.cwd(), tpl));

// URL-based alternative
const dataUrl = new URL("./package.json", import.meta.url);
console.log(fileURLToPath(dataUrl));
const pkg = JSON.parse(await readFile(dataUrl, "utf8"));  // fs accepts URLs

console.log(os.availableParallelism(), "cores,", os.tmpdir());

Remember

  • Use path.join/path.resolve, never string concatenation
  • Relative paths resolve against process.cwd(), not the module's folder
  • Anchor on import.meta.dirname or new URL(..., import.meta.url)

Watch out for

  • Using path.join to build URLs on Windows — it inserts backslashes; use path.posix or URL.

Check yourself

  1. A script in /app/tools/gen.js reads path.resolve("tpl.txt"). The user runs `node tools/gen.js` from /app. Which file is read?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: nodejs.org/docs/latest-v22.x/api/path.html