수요일

🔍 검색 키워드: Node.js ENOENT 해결, ENOENT no such file or directory, Node.js 파일 경로 에러, __dirname 경로 에러, fs.readFile ENOENT, Node.js path 모듈

Node.js 개발하다 보면 파일 읽기/쓰기 코드에서 자주 만나는 에러다. 로컬에서는 잘 되는데 서버에 올리면 갑자기 죽는 경우도 이 에러 때문인 경우가 많다.

Error: ENOENT: no such file or directory, open '/app/config/settings.json'
    at Object.openSync (fs.js:476:3)
    at Object.readFileSync (fs.js:377:35)

ENOENT는 Error NO ENTry의 약자다. 운영체제가 해당 경로에서 파일을 찾을 수 없다는 뜻이다.


증상

  • fs.readFile, fs.readFileSync, fs.writeFile 등 파일 시스템 작업에서 에러
  • 로컬에서는 잘 동작하는데 서버/Docker에서는 에러 발생
  • require('./config') 또는 import 구문에서 모듈을 찾지 못하는 에러
  • 상대 경로로 파일을 읽는데 실행 위치에 따라 동작이 달라짐

원인

원인 1: 상대 경로 기준점 오해

Node.js에서 상대 경로는 프로세스를 실행한 디렉토리(CWD)를 기준으로 한다. 파일이 있는 디렉토리가 아니다.

# 프로젝트 구조
/project/
  src/
    utils/
      fileReader.js  ← 여기서 './config.json' 읽으려 함
  config.json

# 이렇게 실행하면
cd /project/src/utils
node fileReader.js
# './config.json'은 /project/src/utils/config.json을 찾음 → 없음 → ENOENT

원인 2: __dirname vs process.cwd() 혼용

// process.cwd() → 프로세스 실행 위치 (실행할 때마다 달라질 수 있음)
// __dirname   → 현재 파일이 있는 디렉토리 (항상 고정)

// ❌ CWD 기준 — 실행 위치에 따라 경로가 달라짐
fs.readFileSync('./config.json');

// ✅ __dirname 기준 — 항상 이 파일 기준으로 경로 계산
fs.readFileSync(path.join(__dirname, 'config.json'));

해결방법

방법 1: __dirname + path.join() 사용 (CJS)

const fs = require('fs');
const path = require('path');

// ✅ 항상 이 파일 위치 기준으로 경로 계산
const configPath = path.join(__dirname, 'config', 'settings.json');
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));

// 상위 디렉토리 올라갈 때
const rootConfigPath = path.join(__dirname, '..', '..', 'config.json');

방법 2: ES Modules에서 __dirname 대체

// ESM에서는 __dirname이 없음 — 이렇게 구현
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import { readFileSync } from 'fs';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const config = JSON.parse(
  readFileSync(join(__dirname, 'config.json'), 'utf-8')
);

방법 3: 파일 존재 여부 사전 확인 + 에러 처리

try {
  const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
  return config;
} catch (err) {
  if (err.code === 'ENOENT') {
    console.error(`파일을 찾을 수 없습니다: ${configPath}`);
    return defaultConfig;
  }
  throw err; // ENOENT가 아닌 다른 에러는 다시 던짐
}

방법 4: 파일 쓰기 전 디렉토리 자동 생성

function writeFileWithDir(filePath, data) {
  const dir = path.dirname(filePath);
  if (!fs.existsSync(dir)) {
    fs.mkdirSync(dir, { recursive: true });
  }
  fs.writeFileSync(filePath, data, 'utf-8');
}

writeFileWithDir(
  path.join(__dirname, 'output', 'reports', '2026-07-01.json'),
  JSON.stringify(data, null, 2)
);

Docker 환경에서의 ENOENT

# ✅ Dockerfile에서 필요한 디렉토리 미리 생성
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN mkdir -p /app/uploads /app/logs /app/tmp
EXPOSE 3000
CMD ["node", "server.js"]
// ✅ 앱 시작 시 디렉토리 보장
const dirs = ['uploads', 'logs', 'tmp'].map(d => path.join(__dirname, d));
dirs.forEach(dir => fs.mkdirSync(dir, { recursive: true }));

디버깅 방법

// 어느 경로를 보고 있는지 출력
console.log('CWD:', process.cwd());
console.log('__dirname:', __dirname);
console.log('찾는 경로:', path.join(__dirname, 'config.json'));
console.log('파일 존재 여부:', fs.existsSync(path.join(__dirname, 'config.json')));

점검 체크리스트

상황 확인 항목
상대 경로 쓸 때 path.join(__dirname, '...') 형식인지
ES Modules import.meta.url__dirname 재구현했는지
Docker 배포 필요한 디렉토리가 이미지 안에 있는지
파일 쓰기 전 상위 디렉토리 존재하는지 확인
에러 처리 err.code === 'ENOENT' 분기 처리했는지
디버깅 시 process.cwd()__dirname 출력해서 확인

정리

ENOENT 에러는 대부분 두 가지 중 하나다. 경로 기준점을 잘못 잡았거나, 파일이 실제로 없는 것이다.

코드 레벨에서는 path.join(__dirname, ...) 패턴을 습관으로 만들면 실행 위치에 무관하게 안정적으로 동작한다. 배포 환경 이슈라면 Docker 이미지에 필요한 디렉토리가 포함됐는지 확인하고, 앱 시작 시 자동으로 디렉토리를 생성하는 코드를 추가하는 게 가장 확실한 방법이다.

댓글 없음:

댓글 쓰기