[2026] Node.js 파일 시스템 | fs 모듈 완벽 가이드
이 글의 핵심
Node.js 파일 시스템: fs 모듈 파일 읽기·파일 쓰기.
들어가며
fs 모듈이란?
fs (File System) 모듈은 Node.js에서 파일과 디렉토리를 다루는 내장 모듈입니다.
디스크에서 읽고 쓰는 일은 네트워크와 마찬가지로 시간이 걸리는 I/O라서, 서버 코드에서는 readFile 같은 비동기 API를 쓰는 것이 기본입니다. 동기(readFileSync)는 스레드 전체가 멈춘 것처럼 다음 요청을 못 받을 수 있으니, 초기화 스크립트나 CLI처럼 짧게 쓸 때만 쓰는 편이 안전합니다.
주요 기능:
- ✅ 파일 읽기/쓰기
- ✅ 디렉토리 생성/삭제
- ✅ 파일 정보 조회
- ✅ 파일 감시 (watch)
- ✅ 스트림 처리 세 가지 API 스타일:
- 동기 (Sync): 작업이 끝날 때까지 대기
- 비동기 (Callback): 콜백 함수로 결과 처리
- 비동기 (Promise): Promise로 결과 처리 (권장)
실무에서 마주한 현실
개발을 배울 때는 모든 게 깔끔하고 이론적입니다. 하지만 실무는 다릅니다. 레거시 코드와 씨름하고, 급한 일정에 쫓기고, 예상치 못한 버그와 마주합니다. 이 글에서 다루는 내용도 처음엔 이론으로 배웠지만, 실제 프로젝트에 적용하면서 “아, 이래서 이렇게 설계하는구나” 하고 깨달은 것들입니다. 특히 기억에 남는 건 첫 프로젝트에서 겪은 시행착오입니다. 책에서 배운 대로 했는데 왜 안 되는지 몰라 며칠을 헤맸죠. 결국 선배 개발자의 코드 리뷰를 통해 문제를 발견했고, 그 과정에서 많은 걸 배웠습니다. 이 글에서는 이론뿐 아니라 실전에서 마주칠 수 있는 함정들과 해결 방법을 함께 다루겠습니다.
1. 파일 읽기
동기 방식
아래 코드는 javascript를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
const fs = require('fs');
try {
const data = fs.readFileSync('file.txt', 'utf8');
console.log('파일 내용:', data);
} catch (err) {
console.error('에러:', err.message);
}
console.log('다음 코드');
// 출력 순서: 파일 내용 → 다음 코드
주의: 동기 방식은 서버를 블로킹하므로 서버 코드에서는 사용 금지!
비동기 (Callback)
아래 코드는 javascript를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
const fs = require('fs');
fs.readFile('file.txt', 'utf8', (err, data) => {
if (err) {
console.error('에러:', err.message);
return;
}
console.log('파일 내용:', data);
});
console.log('다음 코드');
// 출력 순서: 다음 코드 → 파일 내용
비동기 (Promise) - 권장
Promise 기반 API는 async/await와 함께 사용하여 가장 깔끔한 코드를 작성할 수 있습니다:
다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// fs.promises: Promise 기반 API
const fs = require('fs').promises;
async function readFile() {
try {
// await: Promise가 완료될 때까지 대기
// 코드는 동기처럼 보이지만 실제로는 비동기
// 다른 작업을 블로킹하지 않음
const data = await fs.readFile('file.txt', 'utf8');
// 'utf8': 텍스트 인코딩 지정
// 생략하면 Buffer 객체 반환
console.log('파일 내용:', data);
// data: 파일의 텍스트 내용 (string)
} catch (err) {
// 파일이 없거나 권한 문제 등 에러 발생 시
console.error('에러:', err.message);
// 에러 코드로 세부 처리
if (err.code === 'ENOENT') {
console.error('파일이 존재하지 않습니다');
} else if (err.code === 'EACCES') {
console.error('파일 접근 권한이 없습니다');
}
}
}
// async 함수 호출
readFile();
// 함수가 즉시 반환되고 파일 읽기는 백그라운드에서 진행
console.log('다음 코드');
// 출력 순서: "다음 코드" → "파일 내용: ..."
여러 파일 순차 읽기: 아래 코드는 javascript를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
async function readMultipleFiles() {
try {
// 순차 실행 (하나씩)
const file1 = await fs.readFile('file1.txt', 'utf8');
const file2 = await fs.readFile('file2.txt', 'utf8');
const file3 = await fs.readFile('file3.txt', 'utf8');
console.log('모든 파일 읽기 완료');
} catch (err) {
console.error('에러:', err.message);
}
}
여러 파일 병렬 읽기 (더 빠름): 다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
async function readFilesParallel() {
try {
// Promise.all: 모든 Promise를 동시에 실행
const [file1, file2, file3] = await Promise.all([
fs.readFile('file1.txt', 'utf8'),
fs.readFile('file2.txt', 'utf8'),
fs.readFile('file3.txt', 'utf8')
]);
// 3개 파일을 동시에 읽음 (병렬)
// 가장 느린 파일이 끝나면 모두 완료
console.log('모든 파일 읽기 완료');
} catch (err) {
// 하나라도 실패하면 전체 실패
console.error('에러:', err.message);
}
}
인코딩
아래 코드는 javascript를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// 텍스트 파일 (UTF-8)
const text = await fs.readFile('text.txt', 'utf8');
// 바이너리 파일 (Buffer)
const buffer = await fs.readFile('image.png');
console.log(buffer); // <Buffer 89 50 4e 47 ...>
// Buffer를 문자열로 변환
const str = buffer.toString('utf8');
// Buffer를 Base64로 변환
const base64 = buffer.toString('base64');
2. 파일 쓰기
기본 쓰기
다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
const fs = require('fs').promises;
async function writeFile() {
try {
// 파일 쓰기 (덮어쓰기)
await fs.writeFile('output.txt', 'Hello, Node.js!', 'utf8');
console.log('파일 쓰기 완료');
// 파일 추가 (append)
await fs.appendFile('output.txt', '\n추가 내용', 'utf8');
console.log('내용 추가 완료');
} catch (err) {
console.error('에러:', err.message);
}
}
writeFile();
JSON 파일 처리
JSON 파일을 읽고 쓰는 유틸리티 함수입니다: 다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
const fs = require('fs').promises;
// JSON 읽기 함수
async function readJSON(filename) {
try {
// 1. 파일을 텍스트로 읽기
const data = await fs.readFile(filename, 'utf8');
// data: JSON 형식의 문자열
// 2. JSON 문자열을 JavaScript 객체로 변환
return JSON.parse(data);
// JSON.parse(): 문자열 → 객체
} catch (err) {
// 에러 코드별 처리
if (err.code === 'ENOENT') {
// ENOENT: Error NO ENTry (파일 없음)
return null; // 파일이 없으면 null 반환
}
// 다른 에러는 상위로 전파
throw err;
}
}
// JSON 쓰기 함수
async function writeJSON(filename, data) {
// 1. JavaScript 객체를 JSON 문자열로 변환
const json = JSON.stringify(data, null, 2);
// JSON.stringify(값, replacer, 들여쓰기)
// null: replacer 없음 (모든 속성 포함)
// 2: 들여쓰기 2칸 (가독성 향상)
// 2. JSON 문자열을 파일에 쓰기
await fs.writeFile(filename, json, 'utf8');
}
// 사용 예제
async function main() {
// 1. 데이터 준비
const users = [
{ id: 1, name: '홍길동', age: 25 },
{ id: 2, name: '김철수', age: 30 }
];
// 2. JSON 파일로 저장
await writeJSON('users.json', users);
console.log('users.json 저장 완료');
// 저장된 파일 내용:
// [
// {
// "id": 1,
// "name": "홍길동",
// "age": 25
// },
// {
// "id": 2,
// "name": "김철수",
// "age": 30
// }
// ]
// 3. JSON 파일 읽기
const loadedUsers = await readJSON('users.json');
console.log(loadedUsers);
// [
// { id: 1, name: '홍길동', age: 25 },
// { id: 2, name: '김철수', age: 30 }
// ]
// 4. 데이터 수정 후 다시 저장
loadedUsers[0].age = 26;
await writeJSON('users.json', loadedUsers);
console.log('users.json 업데이트 완료');
}
main().catch(console.error);
// catch: main 함수에서 발생한 에러 처리
실전 예시: 설정 파일 관리: 다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// config.json 읽기/쓰기
const CONFIG_FILE = 'config.json';
async function loadConfig() {
const config = await readJSON(CONFIG_FILE);
// 파일이 없으면 기본 설정 반환
return config || {
port: 3000,
host: 'localhost',
debug: false
};
}
async function saveConfig(config) {
await writeJSON(CONFIG_FILE, config);
}
// 사용
const config = await loadConfig();
config.port = 8080; // 포트 변경
await saveConfig(config); // 저장
3. 파일 정보 및 관리
파일 정보 (stat)
다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
const fs = require('fs').promises;
async function fileInfo(filename) {
try {
const stats = await fs.stat(filename);
console.log('파일 크기:', stats.size, 'bytes');
console.log('생성 시간:', stats.birthtime);
console.log('수정 시간:', stats.mtime);
console.log('접근 시간:', stats.atime);
console.log('파일인가?', stats.isFile());
console.log('디렉토리인가?', stats.isDirectory());
console.log('심볼릭 링크인가?', stats.isSymbolicLink());
} catch (err) {
console.error('에러:', err.message);
}
}
fileInfo('file.txt');
파일 존재 확인
다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
const fs = require('fs').promises;
async function fileExists(filename) {
try {
await fs.access(filename);
return true;
} catch {
return false;
}
}
// 사용
if (await fileExists('file.txt')) {
console.log('파일 존재');
} else {
console.log('파일 없음');
}
파일 작업
다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
const fs = require('fs').promises;
async function fileOperations() {
try {
// 파일 복사
await fs.copyFile('source.txt', 'destination.txt');
console.log('파일 복사 완료');
// 파일 이름 변경 / 이동
await fs.rename('old-name.txt', 'new-name.txt');
console.log('파일 이름 변경 완료');
// 파일 삭제
await fs.unlink('file-to-delete.txt');
console.log('파일 삭제 완료');
// 파일 권한 변경 (Linux/macOS)
await fs.chmod('file.txt', 0o755);
} catch (err) {
console.error('에러:', err.message);
}
}
fileOperations();
4. 디렉토리 관리
디렉토리 생성
다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
const fs = require('fs').promises;
const path = require('path');
async function createDirectories() {
try {
// 단일 디렉토리
await fs.mkdir('new-folder');
// 중첩 디렉토리 (recursive)
await fs.mkdir('parent/child/grandchild', { recursive: true });
console.log('디렉토리 생성 완료');
} catch (err) {
if (err.code === 'EEXIST') {
console.log('디렉토리가 이미 존재합니다');
} else {
console.error('에러:', err.message);
}
}
}
createDirectories();
디렉토리 읽기
다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
const fs = require('fs').promises;
const path = require('path');
async function listFiles(directory) {
try {
const files = await fs.readdir(directory);
console.log(`${directory} 내용:`);
for (const file of files) {
console.log(`- ${file}`);
}
} catch (err) {
console.error('에러:', err.message);
}
}
// 상세 정보 포함
async function listFilesDetailed(directory) {
try {
const files = await fs.readdir(directory, { withFileTypes: true });
for (const file of files) {
const type = file.isDirectory() ? '[DIR]' : '[FILE]';
console.log(`${type} ${file.name}`);
}
} catch (err) {
console.error('에러:', err.message);
}
}
listFiles('.');
listFilesDetailed('.');
재귀적 디렉토리 탐색
다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
const fs = require('fs').promises;
const path = require('path');
async function walkDirectory(directory, callback) {
const files = await fs.readdir(directory, { withFileTypes: true });
for (const file of files) {
const fullPath = path.join(directory, file.name);
if (file.isDirectory()) {
await walkDirectory(fullPath, callback);
} else {
await callback(fullPath);
}
}
}
// 사용: 모든 .js 파일 찾기
async function findJSFiles(directory) {
const jsFiles = [];
await walkDirectory(directory, async (filePath) => {
if (path.extname(filePath) === '.js') {
jsFiles.push(filePath);
}
});
return jsFiles;
}
// 실행
findJSFiles('./src').then(files => {
console.log('JS 파일:', files);
});
디렉토리 삭제
다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
const fs = require('fs').promises;
async function removeDirectory() {
try {
// 빈 디렉토리 삭제
await fs.rmdir('empty-folder');
// 디렉토리와 내용 모두 삭제 (recursive)
await fs.rm('folder-with-files', { recursive: true, force: true });
console.log('디렉토리 삭제 완료');
} catch (err) {
console.error('에러:', err.message);
}
}
removeDirectory();
5. 스트림 (Stream)
읽기 스트림
다음은 javascript를 활용한 상세한 구현 코드입니다. 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
const fs = require('fs');
const readStream = fs.createReadStream('large-file.txt', {
encoding: 'utf8',
highWaterMark: 64 * 1024 // 64KB 청크
});
let totalSize = 0;
readStream.on('data', (chunk) => {
totalSize += chunk.length;
console.log(`청크 받음: ${chunk.length} bytes`);
});
readStream.on('end', () => {
console.log(`총 ${totalSize} bytes 읽음`);
});
readStream.on('error', (err) => {
console.error('에러:', err.message);
});
쓰기 스트림
다음은 javascript를 활용한 상세한 구현 코드입니다. 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
const fs = require('fs');
const writeStream = fs.createWriteStream('output.txt');
writeStream.write('첫 번째 줄\n');
writeStream.write('두 번째 줄\n');
writeStream.write('세 번째 줄\n');
writeStream.end('마지막 줄\n');
writeStream.on('finish', () => {
console.log('파일 쓰기 완료');
});
writeStream.on('error', (err) => {
console.error('에러:', err.message);
});
파이프 (Pipe)
다음은 javascript를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
const fs = require('fs');
// 파일 복사
fs.createReadStream('input.txt')
.pipe(fs.createWriteStream('output.txt'));
// 진행률 표시
const readStream = fs.createReadStream('large-file.txt');
const writeStream = fs.createWriteStream('copy.txt');
let totalSize = 0;
readStream.on('data', (chunk) => {
totalSize += chunk.length;
process.stdout.write(`\r복사 중: ${(totalSize / 1024 / 1024).toFixed(2)} MB`);
});
readStream.on('end', () => {
console.log('\n복사 완료');
});
readStream.pipe(writeStream);
압축
다음은 javascript를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
const fs = require('fs');
const zlib = require('zlib');
// 파일 압축
fs.createReadStream('input.txt')
.pipe(zlib.createGzip())
.pipe(fs.createWriteStream('input.txt.gz'));
// 파일 압축 해제
fs.createReadStream('input.txt.gz')
.pipe(zlib.createGunzip())
.pipe(fs.createWriteStream('output.txt'));
// 여러 변환 체이닝
const { Transform } = require('stream');
class UpperCaseTransform extends Transform {
_transform(chunk, encoding, callback) {
this.push(chunk.toString().toUpperCase());
callback();
}
}
fs.createReadStream('input.txt')
.pipe(new UpperCaseTransform())
.pipe(zlib.createGzip())
.pipe(fs.createWriteStream('output.txt.gz'));
6. 파일 감시 (Watch)
fs.watch
아래 코드는 javascript를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
const fs = require('fs');
const watcher = fs.watch('watched-folder', { recursive: true }, (eventType, filename) => {
console.log(`이벤트: ${eventType}`);
console.log(`파일: ${filename}`);
});
// 감시 중지
setTimeout(() => {
watcher.close();
console.log('감시 중지');
}, 60000);
fs.watchFile (폴링 방식)
아래 코드는 javascript를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
const fs = require('fs');
fs.watchFile('file.txt', { interval: 1000 }, (curr, prev) => {
console.log('파일 변경됨');
console.log('이전 수정 시간:', prev.mtime);
console.log('현재 수정 시간:', curr.mtime);
});
// 감시 중지
setTimeout(() => {
fs.unwatchFile('file.txt');
}, 60000);
chokidar (권장)
npm install chokidar
다음은 javascript를 활용한 상세한 구현 코드입니다. 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
const chokidar = require('chokidar');
const watcher = chokidar.watch('src/**/*.js', {
ignored: /(^|[\/\\])\../, // 숨김 파일 제외
persistent: true
});
watcher
.on('add', path => console.log(`파일 추가: ${path}`))
.on('change', path => console.log(`파일 변경: ${path}`))
.on('unlink', path => console.log(`파일 삭제: ${path}`))
.on('addDir', path => console.log(`디렉토리 추가: ${path}`))
.on('unlinkDir', path => console.log(`디렉토리 삭제: ${path}`))
.on('error', error => console.error(`에러: ${error}`))
.on('ready', () => console.log('초기 스캔 완료'));
// 감시 중지
setTimeout(() => {
watcher.close();
}, 60000);
7. 실전 예제
예제 1: 파일 백업 시스템
다음은 javascript를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
const fs = require('fs').promises;
const path = require('path');
class BackupManager {
constructor(sourceDir, backupDir) {
this.sourceDir = sourceDir;
this.backupDir = backupDir;
}
async backup() {
try {
// 백업 디렉토리 생성
await fs.mkdir(this.backupDir, { recursive: true });
// 타임스탬프
const timestamp = new Date().toISOString().replace(/:/g, '-');
const backupPath = path.join(this.backupDir, `backup-${timestamp}`);
await fs.mkdir(backupPath);
// 파일 복사
const files = await fs.readdir(this.sourceDir);
for (const file of files) {
const sourcePath = path.join(this.sourceDir, file);
const destPath = path.join(backupPath, file);
const stats = await fs.stat(sourcePath);
if (stats.isFile()) {
await fs.copyFile(sourcePath, destPath);
console.log(`백업: ${file}`);
}
}
console.log(`백업 완료: ${backupPath}`);
return backupPath;
} catch (err) {
console.error('백업 실패:', err.message);
throw err;
}
}
async restore(backupPath) {
try {
const files = await fs.readdir(backupPath);
for (const file of files) {
const sourcePath = path.join(backupPath, file);
const destPath = path.join(this.sourceDir, file);
await fs.copyFile(sourcePath, destPath);
console.log(`복원: ${file}`);
}
console.log('복원 완료');
} catch (err) {
console.error('복원 실패:', err.message);
throw err;
}
}
}
// 사용
async function main() {
const manager = new BackupManager('./data', './backups');
// 백업
const backupPath = await manager.backup();
// 복원
// await manager.restore(backupPath);
}
main();
예제 2: 로그 파일 관리
다음은 javascript를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
const fs = require('fs');
const path = require('path');
class Logger {
constructor(logDir = './logs') {
this.logDir = logDir;
this.currentLogFile = null;
this.writeStream = null;
this.ensureLogDir();
this.rotateLog();
}
ensureLogDir() {
if (!fs.existsSync(this.logDir)) {
fs.mkdirSync(this.logDir, { recursive: true });
}
}
rotateLog() {
// 현재 날짜로 로그 파일 생성
const date = new Date().toISOString().split('T')[0];
const logFile = path.join(this.logDir, `app-${date}.log`);
if (this.currentLogFile !== logFile) {
if (this.writeStream) {
this.writeStream.end();
}
this.currentLogFile = logFile;
this.writeStream = fs.createWriteStream(logFile, { flags: 'a' });
}
}
log(level, message) {
this.rotateLog();
const timestamp = new Date().toISOString();
const logMessage = `[${timestamp}] [${level}] ${message}\n`;
// 콘솔 출력
console.log(logMessage.trim());
// 파일 저장
this.writeStream.write(logMessage);
}
info(message) {
this.log('INFO', message);
}
error(message) {
this.log('ERROR', message);
}
warn(message) {
this.log('WARN', message);
}
async cleanup(daysToKeep = 7) {
try {
const files = await fs.promises.readdir(this.logDir);
const now = Date.now();
const maxAge = daysToKeep * 24 * 60 * 60 * 1000;
for (const file of files) {
const filePath = path.join(this.logDir, file);
const stats = await fs.promises.stat(filePath);
if (now - stats.mtime.getTime() > maxAge) {
await fs.promises.unlink(filePath);
console.log(`오래된 로그 삭제: ${file}`);
}
}
} catch (err) {
console.error('정리 실패:', err.message);
}
}
}
// 사용
const logger = new Logger('./logs');
logger.info('서버 시작');
logger.error('데이터베이스 연결 실패');
logger.warn('메모리 사용량 높음');
// 7일 이상 된 로그 삭제
logger.cleanup(7);
예제 3: 파일 검색
다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
const fs = require('fs').promises;
const path = require('path');
async function searchFiles(directory, pattern) {
const results = [];
async function search(dir) {
const files = await fs.readdir(dir, { withFileTypes: true });
for (const file of files) {
const fullPath = path.join(dir, file.name);
if (file.isDirectory()) {
await search(fullPath);
} else {
// 파일명 패턴 매칭
if (pattern.test(file.name)) {
const stats = await fs.stat(fullPath);
results.push({
path: fullPath,
name: file.name,
size: stats.size,
modified: stats.mtime
});
}
}
}
}
await search(directory);
return results;
}
// 사용: 모든 .md 파일 찾기
async function main() {
const mdFiles = await searchFiles('./src', /\.md$/);
console.log(`${mdFiles.length}개 파일 발견:`);
mdFiles.forEach(file => {
console.log(`- ${file.name} (${file.size} bytes)`);
});
}
main();
예제 4: CSV 파일 처리
다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
const fs = require('fs');
const readline = require('readline');
async function processCSV(filename) {
const fileStream = fs.createReadStream(filename);
const rl = readline.createInterface({
input: fileStream,
crlfDelay: Infinity
});
const results = [];
let isFirstLine = true;
let headers = [];
for await (const line of rl) {
if (isFirstLine) {
headers = line.split(',');
isFirstLine = false;
continue;
}
const values = line.split(',');
const obj = {};
headers.forEach((header, index) => {
obj[header.trim()] = values[index]?.trim() || ';
});
results.push(obj);
}
return results;
}
// 사용
async function main() {
const data = await processCSV('users.csv');
console.log('데이터:', data);
// [
// { name: '홍길동', age: '25', email: 'hong@example.com' },
// { name: '김철수', age: '30', email: 'kim@example.com' }
// ]
}
main();
8. 에러 처리
에러 코드
다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
const fs = require('fs').promises;
async function handleErrors() {
try {
const data = await fs.readFile('nonexistent.txt', 'utf8');
} catch (err) {
switch (err.code) {
case 'ENOENT':
console.error('파일을 찾을 수 없습니다');
break;
case 'EACCES':
console.error('권한이 없습니다');
break;
case 'EISDIR':
console.error('디렉토리입니다');
break;
case 'ENOTDIR':
console.error('디렉토리가 아닙니다');
break;
case 'EEXIST':
console.error('이미 존재합니다');
break;
default:
console.error('에러:', err.message);
}
}
}
handleErrors();
안전한 파일 작업
다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
const fs = require('fs').promises;
const path = require('path');
async function safeFileOperation(filename, operation) {
const tempFile = `${filename}.tmp`;
try {
// 임시 파일에 작업
await operation(tempFile);
// 성공하면 원본 파일로 이동
await fs.rename(tempFile, filename);
console.log('작업 완료');
} catch (err) {
// 실패하면 임시 파일 삭제
try {
await fs.unlink(tempFile);
} catch {}
console.error('작업 실패:', err.message);
throw err;
}
}
// 사용
safeFileOperation('data.json', async (tempFile) => {
const data = { users: [] };
await fs.writeFile(tempFile, JSON.stringify(data, null, 2), 'utf8');
});
9. 성능 최적화
버퍼 크기 조정
아래 코드는 javascript를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
const fs = require('fs');
// 작은 버퍼 (느림)
const stream1 = fs.createReadStream('file.txt', {
highWaterMark: 16 * 1024 // 16KB
});
// 큰 버퍼 (빠름, 메모리 많이 사용)
const stream2 = fs.createReadStream('file.txt', {
highWaterMark: 256 * 1024 // 256KB
});
병렬 처리
다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
const fs = require('fs').promises;
// ❌ 순차 처리 (느림)
async function sequential(files) {
const results = [];
for (const file of files) {
const data = await fs.readFile(file, 'utf8');
results.push(data);
}
return results;
}
// ✅ 병렬 처리 (빠름)
async function parallel(files) {
const promises = files.map(file => fs.readFile(file, 'utf8'));
return Promise.all(promises);
}
// 사용
const files = ['file1.txt', 'file2.txt', 'file3.txt'];
// 순차: 약 3초
await sequential(files);
// 병렬: 약 1초
await parallel(files);
메모리 효율
다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
const fs = require('fs');
// ❌ 전체 파일을 메모리에 로드 (대용량 파일에 부적합)
async function inefficient(filename) {
const data = await fs.promises.readFile(filename, 'utf8');
return data.split('\n').length;
}
// ✅ 스트림 사용 (메모리 효율적)
async function efficient(filename) {
return new Promise((resolve, reject) => {
let lineCount = 0;
const stream = fs.createReadStream(filename);
stream.on('data', (chunk) => {
lineCount += chunk.toString().split('\n').length - 1;
});
stream.on('end', () => {
resolve(lineCount);
});
stream.on('error', reject);
});
}
10. 자주 발생하는 문제
문제 1: 경로 문제
아래 코드는 javascript를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// ❌ 상대 경로 (현재 작업 디렉토리 기준)
fs.readFileSync('file.txt'); // 실행 위치에 따라 다름
// ✅ 절대 경로 (__dirname 사용)
const path = require('path');
const filePath = path.join(__dirname, 'file.txt');
fs.readFileSync(filePath);
문제 2: 인코딩 누락
아래 코드는 javascript를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// ❌ 인코딩 없음 (Buffer 반환)
const data = await fs.readFile('file.txt');
console.log(data); // <Buffer 48 65 6c 6c 6f>
// ✅ 인코딩 지정
const data = await fs.readFile('file.txt', 'utf8');
console.log(data); // "Hello"
문제 3: 동기 함수 사용
다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ 동기 함수 (서버 블로킹)
app.get('/file', (req, res) => {
const data = fs.readFileSync('file.txt', 'utf8');
res.send(data);
});
// ✅ 비동기 함수
app.get('/file', async (req, res) => {
try {
const data = await fs.promises.readFile('file.txt', 'utf8');
res.send(data);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
문제 4: 파일 핸들 누수
다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ 스트림을 닫지 않음
const stream = fs.createReadStream('file.txt');
// ....사용 후 닫지 않으면 메모리 누수
// ✅ 명시적으로 닫기
const stream = fs.createReadStream('file.txt');
stream.on('end', () => {
stream.close();
});
// ✅ 또는 pipeline 사용 (자동으로 정리)
const { pipeline } = require('stream');
const util = require('util');
const pipelineAsync = util.promisify(pipeline);
await pipelineAsync(
fs.createReadStream('input.txt'),
fs.createWriteStream('output.txt')
);
11. 실전 팁
파일 존재 확인 패턴
다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
const fs = require('fs').promises;
// 패턴 1: access 사용
async function exists1(filename) {
try {
await fs.access(filename);
return true;
} catch {
return false;
}
}
// 패턴 2: stat 사용
async function exists2(filename) {
try {
await fs.stat(filename);
return true;
} catch {
return false;
}
}
// 패턴 3: EAFP (Easier to Ask for Forgiveness than Permission)
async function readFileSafe(filename) {
try {
return await fs.readFile(filename, 'utf8');
} catch (err) {
if (err.code === 'ENOENT') {
return null; // 파일 없음
}
throw err; // 다른 에러는 재발생
}
}
디렉토리 비우기
다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
const fs = require('fs').promises;
const path = require('path');
async function emptyDirectory(directory) {
try {
const files = await fs.readdir(directory);
await Promise.all(
files.map(async (file) => {
const filePath = path.join(directory, file);
const stats = await fs.stat(filePath);
if (stats.isDirectory()) {
await fs.rm(filePath, { recursive: true });
} else {
await fs.unlink(filePath);
}
})
);
console.log('디렉토리 비우기 완료');
} catch (err) {
console.error('에러:', err.message);
}
}
emptyDirectory('./temp');
파일 크기 확인
다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
const fs = require('fs').promises;
const path = require('path');
async function getDirectorySize(directory) {
let totalSize = 0;
async function calculate(dir) {
const files = await fs.readdir(dir, { withFileTypes: true });
for (const file of files) {
const fullPath = path.join(dir, file.name);
if (file.isDirectory()) {
await calculate(fullPath);
} else {
const stats = await fs.stat(fullPath);
totalSize += stats.size;
}
}
}
await calculate(directory);
return totalSize;
}
// 사용
async function main() {
const size = await getDirectorySize('./src');
console.log(`디렉토리 크기: ${(size / 1024 / 1024).toFixed(2)} MB`);
}
main();
정리
핵심 요약
- fs 모듈: 파일과 디렉토리 관리
- 세 가지 API: 동기(Sync), 비동기(Callback), 비동기(Promise)
- 권장 방식:
fs.promises사용 (async/await) - 스트림: 대용량 파일 처리, 메모리 효율
- 파일 감시:
fs.watch()또는chokidar - 에러 처리: 에러 코드 확인, try-catch
API 비교
| 작업 | 동기 | 비동기 (Callback) | 비동기 (Promise) |
|---|---|---|---|
| 파일 읽기 | readFileSync | readFile | promises.readFile |
| 파일 쓰기 | writeFileSync | writeFile | promises.writeFile |
| 파일 삭제 | unlinkSync | unlink | promises.unlink |
| 디렉토리 생성 | mkdirSync | mkdir | promises.mkdir |
선택 가이드
동기 방식 사용:
- CLI 도구
- 초기화 코드 (서버 시작 전)
- 간단한 스크립트 비동기 방식 사용 (권장):
- 웹 서버
- API 서버
- 동시 처리가 필요한 경우 스트림 사용:
- 대용량 파일 (100MB+)
- 실시간 처리
- 메모리 제한이 있는 경우
다음 단계
- Node.js 데이터베이스 연동
- Node.js 스트림 심화
- Node.js 테스트
추천 학습 자료
공식 문서:
- Node.js fs 모듈
- Node.js path 모듈
- Node.js stream 모듈 패키지:
- chokidar - 파일 감시
- fs-extra - fs 확장
- glob - 파일 패턴 매칭