Logo

Node.js의 fs 모듈로 파일 입출력 처리하기

이번 포스팅에서는 Node.js에서 파일 입출력 처리를 할 때 사용하는 fs 모듈에 대해서 알아보겠습니다.

fs모듈 불러오기

fs 모듈은 Node.js에 내장되어 있어 있기 때문에 별도의 라이브러리 설치없이 바로 불러와서 사용할 수 있습니다.

CommonJS 모듈 시스템을 사용하는 프로젝트에서는 require 키워드로 불러오고, ES 모듈 시스템을 사용하는 프로젝트에서는 import 키워드를 사용할 수 있습니다.

// CommonJS Modules
const fs = require("fs");
// ES Modules
import fs from "fs";

비동기 함수 vs 동기 함수

fs 모듈는 비동기(asynchronous) API와 동기(synchronous) API를 모두 제공하고 있습니다. 띠라서 본인이 작성하는 프로그램의 성격을 고려하여 둘 중에 어떤 API가 적합한지 판단해야 합니다.

fs 모듈에서 제공하는 비동기 메서드는 미지막 인자로 콜백(callback) 함수를 받고 아무 값도 반환히지 않습니다. 반면에 fs 모듈에서 제공하는 동기 메서드는 결과값을 반환(return)하며 예외(exception)를 일으킬 수 있습니다. 동기 메서드의 이름은 Sync로 끝이 나기 때문에 쉽게 비동기 메서드인지 동기 메서드인지 구분이 가능합니다.

자바스크립트의 비동기 처리의 개념이 없으시다면 관련 포스팅을 먼저 읽어보시기를 추천드립니다.

디렉토리 생성하기

제일 먼저 fs 모듈을 이용해서 디렉토리를 만드는 방법에 대해서 알아보겠습니다. 비동기로 디렉토리를 만들 때는 mkdir() 메서드를 사용합니다.

예를 들어, our-fs라는 디렉토리를 생성하는 코드를 작성해보겠습니다.

fs.mkdir("our-fs", (err) => console.log(err));

동일한 작업을 mkdir() 메서드의 동기 버전인 mkdirSync() 메서드를 사용해서 구현할 수도 있습니다.

try {
  fs.mkdirSync("our-fs");
} catch (err) {
  console.log(err);
}

위 코드를 Node.js로 실행 후에 터미널에서 실제로 디렉토리가 생성되었는지 확인할 수 있을 것입니다.

$ ls -d our-fs
our-fs

디렉토리 삭제하기

비동기로 디렉토리를 삭제할 때는 rmdir() 메서드를 사용합니다.

예를 들어, 방금 생성한 our-fs라는 디렉토리를 삭제하는 코드를 작성해보겠습니다.

fs.rmdir("our-fs", (err) => console.log(err));

동일한 작업을 rmdir() 메서드의 동기 버전인 rmdirSync() 메서드를 사용해서 구현할 수도 있습니다.

try {
  fs.rmdirSync("our-fs");
} catch (err) {
  console.log(err);
}

위 코드를 Node.js로 실행 후에 터미널에서 실제로 디렉토리가 삭제되었는지 확인할 수 있을 것입니다.

$ ls -d our-fs
ls: our-fs: No such file or directory

파일에 데이터 쓰기

fs 모듈은 비동기로 파일에 데이터를 쓸 수 있도록 writeFile() 메서드를 제공하고 있습니다.

예를 들어, test.dat라는 파일에 테스트라는 문자열을 쓰는 코드를 작성해보겠습니다.

const file = "test.dat";
const data = "테스트";
fs.writeFile(file, data, (err) => console.log(err));

동일한 작업을 writeFile() 메서드의 동기 버전인 writeFileSync() 메서드를 사용해서 구현할 수도 있습니다.

try {
  const file = "test.dat";
  const data = "테스트";
  fs.writeFileSync(file, data);
} catch (err) {
  console.log(err);
}

위 코드를 Node.js로 실행 후에 터미널에서 실제로 파일에 데이터가 쓰여졌는지 확인할 수 있을 것입니다.

$ cat test.dat
테스트%

한가지 주의할 점은 writeFile() 메서드를 사용하면 기존에 파일에 있던 데이터를 덮어쓴다는 것인데요. 기존에 파일에 있던 데이터 뒤에 새로운 데이터를 추가하고 싶다면 대신에 appendFile() 또는 appendFileSync() 메서드를 사용해야 합니다.

const file = "test.dat";
const data = "추가분";
fs.appendFile(file, data, (err) => console.log(err));
try {
  const file = "test.dat";
  const data = "추가분";
  fs.appendFileSync(file, data);
} catch (err) {
  console.log(err);
}

위 코드를 Node.js로 실행 후에 터미널에서 실제로 파일의 기존 데이터에 새로운 데이터가 추가되었는지 확인할 수 있을 것입니다.

$ cat test.dat
테스트추가분%

파일로 부터 데이터 읽기

fs 모듈의 readFile() 메서드를 사용하면 비동기로 파일로 부터 데이터을 읽을 수 있습니다.

fs.readFile("test.dat", "utf8", (err, data) => {
  if (err) {
    console.error(err);
  } else {
    console.log(data);
  }
});

여기서 주의할 점은 반드시 두번째 인자를 "utf8"로 명시하여 인코딩이 되도록 해줘야 한다는 것입니다. 두번째 인자를 생략하면 콜백 함수의 data 인자로 문자열이 아닌 버퍼(buffer)가 넘어오기 때문에 육안으로 인식이 어렵습니다.

동일한 작업을 readFile() 메서드의 동기 버전인 readFileSync() 메서드를 사용해서 구현해보겠습니다.

try {
  const data = fs.readFileSync("test.dat", "utf8");
  console.log(data);
} catch (err) {
  console.log(err);
}

위 코드를 터미널에서 실행하면 다음과 같이 파일에 저장되어 있는 데이터가 출력될 것입니다.

테스트추가분

파일/디렉토리 메타 정보 확인하기

fs 모듈이 제공하는 메서드 중에서 마지막으로 소개해드릴 메서드는 stat() 입니다. stat() 함수는 파일이나 디렉토리의 메타 정보를 확인할 때 사용합니다.

예를 들어, 위에서 생성한 test.dat 파일의 대한 메타 정보를 출력하는 코드를 작성해보겠습니다.

fs.stat("test.dat", (err, stats) => {
  if (err) {
    console.error(err);
  } else {
    console.log({
      size: stats.size,
      mtime: stats.mtime,
      isFile: stats.isFile(),
    });
  }
});

콜백 함수의 두번째 인자로 넘어오는 stats 객체를 통해 해당 파일이나 디렉토리에 대한 다양한 메타 정보에 접근할 수 있는데요. 예제 코드에서는 파일 크기(size)와 수정 시간(mtime) 그리고 파일인지 디렉토리인지를 반환하는 isFile() 메서드를 호출해보았습니다.

마찬가지로 stat() 메서드의 동기 버전인 statSync() 메서드를 사용해서 동일한 작업을 구현할 수 있습니다.

try {
  const stats = fs.statSync("test.dat");
  console.log({
    size: stats.size,
    mtime: stats.mtime,
    isFile: stats.isFile(),
  });
} catch (err) {
  console.error(err);
}

위 코드를 터미널에서 실행하면 다음과 같이 파일의 메타 정보가 출력될 것입니다.

{ size: 18, mtime: 2021-11-13T03:14:37.596Z, isFile: true }

마치면서

지금까지 많이 쓰이는 기능 위주로 Node.js의 js 모듈을 어떻게 활용할 수 있는지 살펴보았습니다.

비동기로 파일 입출력 처리를 할 때 js 모듈을 이용하면 소위 콜백 지옥에 빠질 수가 있습니다. 따라서 최근에는 Promise를 이용하는 js.promises 모듈이 더 많이 사용되는 추세인데요. 이 부분에 대해서 추후 포스팅를 통해서 다뤄보도록 하겠습니다.