[Nodejs] multer 와 fs 로 웹사이트에서 txt 파일 읽기

devsr·2022년 2월 3일
0
  • 프로젝트 설명 : 사용자가 .txt 파일을 업로드하면 웹사이트가 그 파일을 읽어 보여주는 기능을 추가한다. (편의상, 지난 영화 웹사이트 안에서 추가 서비스를 제공하는 형태로 진행하였다.)

  • 소요기간 : 2022.02.03

  • 전체코드 보기 : 깃허브 링크 바로가기

  • 주요 흐름

  1. /convert 페이지로 들어왔을 때의 모습

  2. 텍스트 파일을 업로드 하였을 때 그 내용이 아래에 표시되는 모습

  3. 목록에 있던 텍스트 파일의 제목을 클릭하면 구체적인 파일 내용을 볼 수 있는 창으로 이동


✔️ Multer (업로드 담당)

Express에서 파일 업로드를 수행 및 처리하기 위해서는 Multer라는 package를 사용하면 된다. 즉, Multer라는 package를 사용해 Node js 에서 파일을 업로드할 수 있게 된 것이다.

추가) 업로드 후 저장되는 filename 을 랜덤이 아니라, 내가 원하는 대로 지정해주기 위해 다음과 같이 storage 를 사용하였다. (If no filename is given, each file will be given a random name that doesn’t include any file extension.)

//src/middlewares.js

//기존코드
export const convertFiles = multer({ dest: "texts/" });

//수정된 코드
let storage = multer.diskStorage({
    destination: function(req, file, cb) {
      cb(null, 'texts/');
    },
    filename: function(req, file, cb) {
      cb(null, file.originalname);
    }
});
export const convertFiles = multer({ storage: storage });


✔️ fs (파일/폴더 접근 담당)

fs는 node.js에 들어 있는 module로 file system의 약자이다. 서버의 파일/폴더에 접근할 수 있는 함수들이 들어 있다. fs 모듈의 메서드를 사용하여 파일을 읽고 쓰는 등의 처리를 할 수 있다.

그 중, 우리가 사용할 메소드는 readdir(폴더 읽기)readFile(파일 읽기) 기능이다.

//src/controllers/convertController.js
import { readdir, readFile } from 'fs';

export const getConvert = (req, res) => {
	//texts 폴더에 있는 파일들 읽기
    readdir('texts', (err, files) => { 
        try{
            console.log(files)
            return res.render("convert/convert.pug", {pageTitle : "추가 서비스: TXT to HTML", files})
        }catch(err){
            return res.render("convert/convert.pug", {pageTitle : "추가 서비스: TXT to HTML"}, {errorMessage : "Error"})
        }
    })
} 

export const postConvert = (req, res) => {
    const { file } = req;
    //texts/${file.filename} 파일 읽기
    readFile(`texts/${file.filename}`,  'utf8', (err, data) => {
        try{
            readdir('texts', (err, files) => { //texts 폴더 안 파일 다시 읽기 (비동기로 하면 참 좋을텐ㄷ.....ㅔ)
                return res.render("convert/convert.pug", {pageTitle : "추가 서비스: TXT to HTML", data, files }
                )}
            )
        } catch(err) {
            return res.render("convert/convert.pug", {pageTitle : "추가 서비스: TXT to HTML"}, {errorMessage : "Error"})
        }
    });
}

export const converDetail = (req, res) => {
    const { 
        params : 
            { id }
    } = req;
    console.log(id);
    readFile(`texts/${id}`,  'utf8', (err, data) => { //texts/${id} 파일 읽기
        try{
            console.log(data)
            return res.render("convert/detail.pug", {pageTitle : `${id}`, data})
        } catch(err) {
            return res.render("convert/detail.pug", {pageTitle : `${id}` , errorMessage : "Error"})
        }
    });
}

그러나, 위 코드의 문제점은 다음과 같다.
1. 파일 및 폴더를 읽는 작업이 들어있으나, 비동기적인 처리가 되어 있지 않다.
2. 파일 및 폴더를 읽는 작업 코드가 반복적으로 들어있다. -> 따로 extract 해보자.



✔️ 비동기처리를 위한 코드

//src/controllers/convertController.js
import fs from "fs";

const getDataFromFilePromise = (filePath, options = null) => {
  return new Promise((resolve, reject) => {
    fs.readFile(filePath, options, function (err, data) {
      if (err) {
        reject(err);
      } else {
        resolve(data);
      }
    });
  });
};

const getFilesFromDirPromise = (path, options = "utf8") => {
  return new Promise((resolve, reject) => {
    fs.readdir(path, options, (err, files) => {
      if (err) {
        reject(err);
      } else {
        resolve(files);
      }
    });
  });
};

export const getConvert = async (req, res) => {
  const files = await getFilesFromDirPromise("/texts");
  return res.render("convert/convert.pug", {pageTitle : "추가 서비스: TXT to HTML", files});
};

export const postConvert = (req, res) => {
  const { file } = req;
  return res.render("convert/convert.pug", {pageTitle : "추가 서비스: TXT to HTML", data, files });
};

export const convertDetail = async (req, res) => {
  const {
    params: { id }
  } = req;
  const data = await getDataFromFilePromise(`/texts/${id}`, "utf8");
  return res.render("convert/detail.pug", {pageTitle : `${id}`, data});
};

✏️ 함수 extract 해서 사용하기

getDataFromFilePromise : 파일로부터 데이터 읽는 코드 (프로미스)
getFilesFromDirPromise : 폴더로부터 파일 읽는 코드 (프로미스)

✏️ Promise 사용

//promise 생성자 : new Promise(){..}
let promise = new Promise(function(resolve, reject) {
  // executor (제작 코드, '가수')
});
  • JavaScript에서 Promise는 비동기적으로 실행하는 작업의 결과(성공 or 실패)를 나타내는 객체입니다. 여기서 주목해야 하는 점은 객체 라는 것인데, 비동기의 결과를 객체화 시킨다는 점이 Promise의 가장 큰 장점이자 특징이 됩니다.
  • new Promise에 전달되는 함수는 executor(실행자, 실행 함수) 라고 부릅니다. executor는 new Promise가 만들어질 때 자동으로 실행되는데, executor는 보통 시간이 걸리는 일을 수행합니다(그리고 그것을 비동기적으로 수행합니다). 일이 끝나면 resolve나 reject 함수를 호출합니다.
    resolve(value) — 일이 성공적으로 끝난 경우 그 결과를 나타내는 value와 함께 호출
    reject(error) — 에러 발생 시 에러 객체를 나타내는 error와 함께 호출

파일로부터 데이터 읽는 코드(getDataFromFilePromise) 와 폴더로부터 파일 읽는 코드(getFilesFromDirPromise) 에서 각각 fs.readFile와 fs.readdir 를 사용하고 있어 시간이 소요되므로, Promise 객체를 사용해준 것이다.

const getDataFromFilePromise = (filePath, options = null) => {
  return new Promise((resolve, reject) => {
    fs.readFile(filePath, options, function (err, data) {
      if (err) {
        reject(err);
      } else {
        resolve(data);
      }
    });
  });
};
const getFilesFromDirPromise = (path, options = "utf8") => {
  return new Promise((resolve, reject) => {
    fs.readdir(path, options, (err, files) => {
      if (err) {
        reject(err);
      } else {
        resolve(files);
      }
    });
  });
};

✏️ async await 사용

  • await('기다리다’라는 뜻을 가진 영단어 – 옮긴이)는 말 그대로 Promise가 처리될 때까지 함수 실행을 기다리게 만듭니다. Promise가 처리되면 그 결과와 함께 실행이 재개되죠.
  • await는 async 함수 안에서만 동작합니다.
  • await는 promise.then보다 좀 더 세련되게 Promise의 결과값이나 에러를 얻을 수 있도록 해주는 문법입니다. promise.then보다 async/await를 사용하는 것이 가독성도 더 좋고 편리합니다.

getConvert 와 convertDetail 에서 getFilesFromDirPromise 와 getDataFromFilePromise 함수를 사용하고 그 결과값을 템플릿에 던지는(결과값 나올 때까지 기다려줘야함) 있기 때문에 async await 사용해줬다.

export const getConvert = async (req, res) => {
  const files = await getFilesFromDirPromise("/texts");
  // 프라미스가 이행될 때까지 기다림. 프라미스가 처리되면 실행이 재개
  return res.render("convert/convert.pug", {pageTitle : "추가 서비스: TXT to HTML", files});
};
export const convertDetail = async (req, res) => {
  const {
    params: { id }
  } = req;
  const data = await getDataFromFilePromise(`/texts/${id}`, "utf8");
  // 프라미스가 이행될 때까지 기다림. 프라미스가 처리되면 실행이 재개
  return res.render("convert/detail.pug", {pageTitle : `${id}`, data});
};

참고:
https://velog.io/@cyranocoding/2019-08-02-1808-%EC%9E%91%EC%84%B1%EB%90%A8-5hjytwqpqj

https://ko.javascript.info/async-await

profile
웹/앱 개발자

0개의 댓글