Node.js 교재 · 7편 · path 와 os

path와 os 모듈 — 경로와 시스템 정보 다루기

"왜 윈도우만 깨지지?" 의 90% 가 여기서 해결된다.

경로 슬래시가 정규화되며 합쳐지는 컨셉 아이소메트릭 일러스트

6편에서 fs 로 파일을 다뤘다. 그런데 파일을 다루다 보면 곧 — "경로" 문제에 부딪친다. 맥·리눅스는 /home/user/file.txt 인데 윈도우는 C:\Users\user\file.txt. 슬래시 방향이 반대다. 직접 문자열로 합치면 한쪽이 무조건 깨진다.

이걸 해결하는 게 path 모듈. 그리고 비슷한 결로 시스템 정보(메모리·CPU·홈 디렉토리)를 얻는 게 os 모듈. 둘 다 작지만 거의 모든 Node 프로그램이 쓴다.

1. path — 크로스 플랫폼 경로의 안전망

가장 자주 부딪치는 실수.

// ❌ 직접 문자열로 합치기 (윈도우에서 깨짐) const file = './data' + '/' + 'users.json'; // ✅ path.join 사용 (어디서나 동작) import path from 'node:path'; const file = path.join('.', 'data', 'users.json');

path.join 은 OS 에 맞는 구분자(/ 또는 \)를 자동으로 골라준다. 또 연속 슬래시·.. 같은 것도 정규화. 직접 문자열을 합치는 순간 크로스 플랫폼 보장이 깨진다고 보면 된다.

2. path 자주 쓰는 메서드 6가지

import path from 'node:path'; // ① 합치기 — 가장 많이 씀 path.join('a', 'b', 'c'); // 'a/b/c' path.join('/home', 'user', 'docs'); // '/home/user/docs' path.join('a', '..', 'b'); // 'b' (정규화됨) // ② 절대 경로로 변환 path.resolve('a/b'); // '/현재/작업/디렉토리/a/b' path.resolve('/x', 'y'); // '/x/y' // ③ 파일 이름 / 확장자 / 디렉토리 분리 path.basename('/home/user/file.txt'); // 'file.txt' path.basename('/home/user/file.txt', '.txt'); // 'file' path.dirname('/home/user/file.txt'); // '/home/user' path.extname('/home/user/file.txt'); // '.txt' // ④ 객체로 한 번에 분해 path.parse('/home/user/file.txt'); // { root: '/', dir: '/home/user', base: 'file.txt', name: 'file', ext: '.txt' } // ⑤ 두 경로의 상대 경로 path.relative('/data/orders', '/data/orders/2026/01.json'); // '2026/01.json' // ⑥ 구분자 자체 (윈도우 \, 그 외 /) path.sep; // '/' or '\\'
join vs resolve 차이join 은 단순히 합쳐 정규화하고, resolve 는 절대 경로로 만든다. 파일 시스템 작업엔 거의 resolve 가 안전 (현재 작업 디렉토리에 안 휘둘림). 단 URL 만들 땐 join 이 편함.

3. __dirname 함정과 ESM 대안

CommonJS 시절엔 __dirname 이 자동 변수로 들어왔다. 현재 파일이 있는 디렉토리. ESM("type": "module")에선 사라졌다.

// ❌ ESM 에선 ReferenceError const config = path.join(__dirname, 'config.json'); // ✅ 표준 ESM 패턴 import { fileURLToPath } from 'node:url'; import path from 'node:path'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const config = path.join(__dirname, 'config.json');

이 4줄이 사실상 모든 ESM Node 파일에 등장하는 보일러플레이트. 보고 외워두면 좋다. Node 21 부터는 import.meta.dirname 한 줄로 단축됐다.

// Node 21+ (현행 LTS 22 는 사용 가능) const __dirname = import.meta.dirname;

4. os — 시스템 정보 얻기

os 모듈은 CPU·메모리·홈 디렉토리·플랫폼·hostname 같은 시스템 정보 창구.

import os from 'node:os'; os.platform(); // 'darwin' | 'linux' | 'win32' os.arch(); // 'x64' | 'arm64' os.cpus().length; // CPU 코어 수 (예: 8) os.totalmem(); // 전체 메모리 (바이트) os.freemem(); // 가용 메모리 (바이트) os.homedir(); // '/home/user' or 'C:\\Users\\user' os.tmpdir(); // '/tmp' or 'C:\\Users\\...\\Temp' os.hostname(); // 'mac-mini-of-jspark' os.userInfo(); // { username, uid, gid, shell, homedir } os.EOL; // '\n' or '\r\n' — 줄바꿈 문자

가장 유용한 두 가지 — os.homedir() (사용자 홈 폴더, 설정 파일 위치 계산용) 와 os.cpus().length (워커 풀 크기 정할 때). os.EOL 도 의외로 자주 — 윈도우 줄바꿈 호환 처리.

5. 실전 예 — 크로스 플랫폼 설정 파일

두 모듈을 같이 쓰는 표준 패턴.

// ~/.myapp/config.json 읽기·생성 import fs from 'node:fs/promises'; import path from 'node:path'; import os from 'node:os'; const APP_DIR = path.join(os.homedir(), '.myapp'); const CONFIG_FILE = path.join(APP_DIR, 'config.json'); async function loadConfig() { try { return JSON.parse(await fs.readFile(CONFIG_FILE, 'utf-8')); } catch (e) { if (e.code === 'ENOENT') { await fs.mkdir(APP_DIR, { recursive: true }); const defaults = { theme: 'dark', lastSync: null }; await fs.writeFile(CONFIG_FILE, JSON.stringify(defaults, null, 2)); return defaults; } throw e; } } const config = await loadConfig(); console.log(`Loaded config from: ${CONFIG_FILE}`);

맥·리눅스에선 ~/.myapp/config.json, 윈도우에선 C:\Users\xx\.myapp\config.json — 코드 한 줄도 안 바꾸고 둘 다 동작. os.homedir() + path.join() 의 조합이 핵심.

윈도우 함정 — 경로의 백슬래시 — JSON·정규식·문자열 리터럴에 윈도우 경로를 넣을 땐 \\ 두 번. 이런 거 신경 쓰기 싫으면 무조건 path.join·path.resolve 만 쓰면 된다. 직접 백슬래시를 타이핑하는 순간 함정.

요약 — 7편 좌표

여기까지 정리. 경로는 절대 직접 문자열로 합치지 말고 path.join·path.resolve 사용. ESM 에선 __dirname 대신 import.meta.dirname (Node 21+) 또는 fileURLToPath 패턴. os.homedir()·os.cpus()·os.tmpdir() 같은 시스템 정보. 설정 파일은 path.join(os.homedir(), '.myapp') 가 크로스 플랫폼 표준. 다음 편에선 Node 의 심장 — 이벤트 루프 의 동작을 본다.

다음 편 예고 — 이벤트 루프 완전 정리

Call Stack · Queue · libuv 가 어떻게 함께 도나. 8편.

© 2026 주나이테크(주) @JUNAITECH