Пошук уроків, статей та іншого контенту
Формуйте надійні шляхи через path та пишіть код, що коректно працює в різних операційних системах.
pathШляхи до файлів відрізняються в різних операційних системах:
Linux і macOS використовують /:
/home/user/project/data/file.jsonWindows зазвичай використовує \:
C:\Users\user\project\data\file.jsonТому ручне складання шляхів через конкатенацію рядків ненадійне:
const filePath = __dirname + "/data/" + "file.json";Такий код може працювати в одній операційній системі, але створювати проблеми в іншій. Для формування шляхів у Node.js використовуйте вбудований модуль node:path.
const path = require('node:path');
const filePath = path.join(__dirname, 'data', 'file.json');
console.log(filePath);path.join() самостійно використовує правильний роздільник для поточної операційної системи.
path.join()Метод path.join() об'єднує кілька частин шляху в один і нормалізує результат:
const path = require('node:path');
const filePath = path.join('project', 'data', 'users.json');
console.log(filePath);Результат буде приблизно таким:
project/data/users.jsonНа Windows Node.js сформує шлях із відповідними роздільниками.
Метод також обробляє . та ..:
const path = require('node:path');
console.log(path.join('project', 'src', '..', 'data'));
// project/data. означає поточний каталог;
.. означає батьківський каталог.
path.join() не обов'язково повертає абсолютний шлях. Якщо всі частини відносні, результат теж буде відносним.
path.resolve()path.resolve() формує абсолютний шлях. Якщо шлях не є абсолютним, Node.js використовує поточний робочий каталог процесу.
const path = require('node:path');
console.log(path.resolve('data', 'users.json'));Якщо програму запустити з каталогу /home/user/project, результатом буде:
/home/user/project/data/users.jsonВажливо розрізняти:
__dirname — каталог поточного JavaScript-файлу в CommonJS;
process.cwd() — каталог, з якого було запущено Node.js;
path.resolve() без абсолютної початкової частини використовує process.cwd().
Наприклад:
const path = require('node:path');
console.log('__dirname:', __dirname);
console.log('process.cwd():', process.cwd());
const fromCurrentWorkingDirectory = path.resolve('data', 'file.json');
const fromThisFile = path.join(__dirname, 'data', 'file.json');
console.log(fromCurrentWorkingDirectory);
console.log(fromThisFile);Ці шляхи можуть бути різними, якщо програму запустити не з кореневого каталогу проєкту.
Якщо ресурс є частиною проєкту та має знаходитися поруч із кодом, зазвичай шлях потрібно будувати відносно поточного файлу:
const path = require('node:path');
const configPath = path.join(__dirname, 'config', 'app.json');
console.log(configPath);Це надійніше, ніж покладатися на поточний каталог запуску:
const path = require('node:path');
// Шлях залежить від каталогу, з якого запустили програму
const unstablePath = path.resolve('config', 'app.json');
// Шлях залежить від розташування цього JavaScript-файлу
const stablePath = path.join(__dirname, 'config', 'app.json');
console.log({ unstablePath, stablePath });Наприклад, такі команди можуть мати різний результат:
node src/app.jscd src
node app.jsУ першому випадку process.cwd() — корінь проєкту, а в другому — каталог src.
Модуль path містить методи для отримання окремих частин шляху.
const path = require('node:path');
const filePath = '/project/data/users.json';
console.log(path.basename(filePath));
// users.json
console.log(path.dirname(filePath));
// /project/data
console.log(path.extname(filePath));
// .json
console.log(path.parse(filePath));
// {
// root: '/',
// dir: '/project/data',
// base: 'users.json',
// ext: '.json',
// name: 'users'
// }path.basename()Повертає останню частину шляху — зазвичай ім'я файлу:
const path = require('node:path');
const fileName = path.basename('/project/data/users.json');
console.log(fileName);
// users.jsonЗа потреби можна отримати ім'я без розширення:
const path = require('node:path');
const name = path.basename('/project/data/users.json', '.json');
console.log(name);
// userspath.dirname()Повертає каталог, у якому знаходиться шлях:
const path = require('node:path');
console.log(path.dirname('/project/data/users.json'));
// /project/datapath.extname()Повертає розширення файлу разом із крапкою:
const path = require('node:path');
console.log(path.extname('report.pdf'));
// .pdf
console.log(path.extname('archive.tar.gz'));
// .gzЯкщо розширення немає, результатом буде порожній рядок:
const path = require('node:path');
console.log(path.extname('README'));
// ''path.parse() і path.format()path.parse() перетворює шлях на об'єкт із його складовими:
const path = require('node:path');
const parts = path.parse('/project/data/users.json');
console.log(parts.name);
// users
console.log(parts.ext);
// .jsonПісля зміни окремих частин шлях можна зібрати назад за допомогою path.format():
const path = require('node:path');
const parsed = path.parse('/project/data/users.json');
const backupPath = path.format({
dir: parsed.dir,
name: `${parsed.name}.backup`,
ext: parsed.ext,
});
console.log(backupPath);
// /project/data/users.backup.jsonВикористовуйте ці методи, коли потрібно змінити ім'я або розширення файлу, а не редагувати шлях як звичайний рядок.
path.relative()path.relative() обчислює відносний шлях від одного каталогу до іншого:
const path = require('node:path');
const projectRoot = '/project';
const filePath = '/project/src/utils/file.js';
console.log(path.relative(projectRoot, filePath));
// src/utils/file.jsЦе корисно, коли потрібно показати шлях користувачу у відносному вигляді або зберегти відносну адресу ресурсу.
Метод не створює файл і не перевіряє, чи існують каталоги. Він лише обчислює рядок шляху.
Для поточної операційної системи модуль path надає властивість sep:
const path = require('node:path');
console.log(path.sep);/ у Linux і macOS;
\ у Windows.
Не варто вручну вставляти path.sep між частинами, якщо можна використати path.join():
const path = require('node:path');
const filePath = ['data', 'reports', 'today.json'].join(path.sep);
console.log(filePath);Краще:
const path = require('node:path');
const filePath = path.join('data', 'reports', 'today.json');
console.log(filePath);Модуль також має path.delimiter — роздільник для змінних середовища на кшталт PATH. Це інший роздільник, не той, що використовується між каталогами:
const path = require('node:path');
console.log(path.delimiter);Зазвичай:
: у Linux і macOS;
; у Windows.
Нехай проєкт має таку структуру:
project/
├── src/
│ └── app.js
├── data/
│ └── users.json
└── exports/Файл src/app.js може побудувати шляхи до даних і каталогу експорту так:
const path = require('node:path');
const projectRoot = path.resolve(__dirname, '..');
const dataDirectory = path.join(projectRoot, 'data');
const usersFile = path.join(dataDirectory, 'users.json');
const exportsDirectory = path.join(projectRoot, 'exports');
const reportFile = path.join(exportsDirectory, 'users-report.json');
console.log({
projectRoot,
dataDirectory,
usersFile,
exportsDirectory,
reportFile,
});Цей код використовує лише операції над рядками, тому він працює однаково в різних операційних системах.
Для операцій із файловою системою отримані шляхи можна передати в node:fs:
const path = require('node:path');
const fs = require('node:fs');
const projectRoot = path.resolve(__dirname, '..');
const usersFile = path.join(projectRoot, 'data', 'users.json');
if (fs.existsSync(usersFile)) {
const content = fs.readFileSync(usersFile, 'utf8');
console.log(content);
} else {
console.log(`Файл не знайдено: ${usersFile}`);
}Тут fs відповідає за роботу з файловою системою, а path — за правильне формування адреси файлу.
У CommonJS доступна глобальна змінна __dirname:
const path = require('node:path');
const filePath = path.join(__dirname, 'data', 'file.json');В ECMAScript modules (.mjs або "type": "module" у package.json) __dirname не визначена. У такому випадку шлях до поточного файлу можна отримати через import.meta.url і fileURLToPath:
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const currentFile = fileURLToPath(import.meta.url);
const currentDirectory = path.dirname(currentFile);
const filePath = path.join(currentDirectory, 'data', 'file.json');
console.log(filePath);fileURLToPath() перетворює URL файлу на звичайний шлях операційної системи. Після цього шлях можна передавати до методів path і fs.
pathЗвичайний path використовує правила поточної операційної системи. Іноді потрібно обробити шлях іншої платформи, наприклад Windows-шлях, отриманий від зовнішньої системи.
Для цього існують:
path.posix — правила POSIX-систем, зокрема Linux і macOS;
path.win32 — правила Windows.
const path = require('node:path');
const windowsPath = String.raw`C:\projects\app\data\users.json`;
console.log(path.win32.basename(windowsPath));
// users.json
console.log(path.win32.dirname(windowsPath));
// C:\projects\app\data
console.log(path.win32.extname(windowsPath));
// .jsonНе використовуйте path.win32 або path.posix без потреби. Для звичайних локальних шляхів використовуйте path.join(), path.resolve() та інші методи звичайного path.
Якщо частина шляху надходить від користувача, не додавайте її до адреси за допомогою конкатенації без перевірки:
const path = require('node:path');
const fileName = '../config.json';
const filePath = path.join('/project/data', fileName);
console.log(filePath);
// /project/config.json.. може вивести шлях за межі очікуваного каталогу. Сам path.join() не є засобом перевірки доступу.
Для задач, де користувач може вказувати ім'я файлу, потрібно:
сформувати абсолютний базовий каталог;
сформувати абсолютний кандидат за допомогою path.resolve();
перевірити, що результат залишився всередині дозволеного каталогу.
const path = require('node:path');
const dataDirectory = path.resolve(__dirname, 'data');
const requestedName = process.argv[2] || 'users.json';
const requestedPath = path.resolve(dataDirectory, requestedName);
const relativePath = path.relative(dataDirectory, requestedPath);
const isInsideDataDirectory =
relativePath === '' ||
(!relativePath.startsWith('..' + path.sep) &&
relativePath !== '..' &&
!path.isAbsolute(relativePath));
if (!isInsideDataDirectory) {
throw new Error('Недозволений шлях до файлу');
}
console.log(requestedPath);Перевірка через path.relative() враховує роздільник поточної операційної системи.
/ або \Неправильно:
const filePath = __dirname + '\\data\\file.json';Такий код прив'язаний до Windows.
Правильно:
const path = require('node:path');
const filePath = path.join(__dirname, 'data', 'file.json');__dirname і process.cwd()process.cwd() змінюється залежно від каталогу запуску програми. Якщо шлях має бути прив'язаний до JavaScript-файлу, використовуйте __dirname у CommonJS або каталог, отриманий із import.meta.url в ESM.
path.join() замість path.resolve()path.join() об'єднує частини, але не гарантує абсолютний результат:
const path = require('node:path');
console.log(path.join('data', 'file.json'));
// data/file.jsonЯкщо потрібен абсолютний шлях, використовуйте:
const path = require('node:path');
console.log(path.resolve('data', 'file.json'));Рядок на кшталт file:///tmp/data.json є URL, а не звичайним шляхом файлової системи. Для перетворення URL файлу використовуйте fileURLToPath().
startsWith()Небезпечно покладатися лише на таку перевірку:
candidate.startsWith(baseDirectory);Наприклад, каталог /project/data-backup починається з рядка /project/data, хоча він не є його дочірнім каталогом. Для перевірки меж каталогу використовуйте path.relative() та враховуйте path.sep.
Для формування шляхів у Node.js використовуйте вбудований модуль node:path.
path.join() об'єднує частини шляху та використовує правильний роздільник.
path.resolve() створює абсолютний шлях.
__dirname посилається на каталог поточного файлу в CommonJS, а process.cwd() — на каталог запуску процесу.
path.basename(), dirname(), extname(), parse() і format() допомагають працювати з окремими частинами шляху.
Не формуйте шляхи через ручну конкатенацію рядків із / або \.
Для шляхів іншої платформи можна використовувати path.posix і path.win32.
Дані від користувача потрібно перевіряти, щоб шлях не вийшов за межі дозволеного каталогу.