개요
파일 업로드를 처리하기 위해 Nest는 Express용 multer 미들웨어 패키지를 기반으로 하는 내장 모듈을 제공한다. multer 는 주로 HTTP POST 요청을 통해 파일을 업로드 하는데 사용되는 multipart/form-data 형식으로 게시된 데이터를 처리한다.
multer는 multipart/form-data 이 아닌 데이터를 처리할 수 없다. 또한 FastifyAdapter와도 호환되지 않는다.
설치
npm i -D @types/multer
JavaScript
복사
사용
•
단일 파일 추가
catsController 내부에 이미지 업로드 메소드인 uploadCatImg 메소드에 @UseInterceptors(FileInterceptor(’image’)) 를 추가한다.
여기서 사용된 image 라는 키워드는 프론트에서 해당 파일이 넘어올때 매핑이 되어있는 키값이다.
프론트에서 image라는 키값을 사용할 것이므로, 여기서 image로 지정
•
파일 배열 추가
여러 파일을 등록하는 로직이라면
매개변수에 (@UploadedFiles() files: Array<Express.Multer.file>)
를 넣어준다.
MulterModule 등록
cats.module.ts
해당 기능을 사용하는 모듈에다가 MulterModule을 등록한다.
여기서는 cats.module에 MulterModule을 등록하면 된다.
그리고 dest: 항목은 파일이 업로드가 되면 어디로 저장할 건 지 지정하는 설정이다.
파일 업로드 하기
이미지 업로드 하기
프론트를 열어서 로그인하고 이미지를 업로드 해보자.
개발자 도구를 열어서 201 ‘Created’ 메시지를 받았다면 잘 작동한 것이다.
upload 폴더 확인
upload 폴더가 생성이 되었고, 파일 또한 생성이 되었다.
파일형식이 정확이 어떤 파일인지 확장자를 포함하지 않아 읽지는 못하고 있다.
MulterOptions
폴더 생성
commons > utils 폴더를 생성하고 multer.options.ts 파일을 생성한다.
multer.options.ts 전체 코드
폴더 생성 로직
→ fs.mkdirSync(path.join(__dirname, ‘..’, ‘uploads’));
현재 파일의 상위 디렉토리를 생성하는 로직으로, 현재 파일의 상위 디렉토리에 ‘uploads’ 폴더를 생성한다. 만약 폴더가 이미 존재한다면 catch블록으로 넘어가게 된다.
→ fs.mkdirSync(path.join(__name, ‘..’, ‘uploads/${folder}’));
특정 folder이름으로 하위폴더를 uploads 디렉토리 내부에 생성하는 로직이다. 이것도 마찬가지로 디렉토리가 이미 존재하면 catch블록으로 넘어간다.
저장 로직
→ 저장 로직은 folder 문자열을 받아서 StorageEngine 을 반환하는 로직이다.
→ destination 함수 :
이 함수는 파일을 어디에 저장할 지 정의한다. path.join(__dirname, ‘..’, ‘uploads/${folder}’); 로직에서 저장할 폴더의 경로를 지정한다.
→ filename 함수 :
ext 는 파일의 확장자를 추출한다.
fileName 변수는 원래 파일명에서 확장자를 제거하고, 현재 시간을 타임스탬프로 추가하여 고유한 파일명을 만든다.
•
ext 사용 예시
파일 확장자를 어떻게 추출하는 지에 대해서 공식문서에 나와 있다.
이미지 업로드
이미지 업로드 해보기
업로드 창에서 이미지를 선택
개발자 도구 확인
개발자 도구에서도 정상 확인
로그 확인
이것에 Express.Multer.File 에 담겨 있는 내용
파일 확인
1.
dist 폴더 확인
우리는 코드를 TypeScript로 구현을 하고 컴파일 되면 JavaScript로 컴파일 되어서 실행된다.
즉, src에서 컴파일 되면 javaScript코드들이 dist폴더에 생성이 되게 되는데, 이 내부의 upload 폴더에 파일이 생성이 되었을 것이다.
2.
파일 확인
dist > common > uploads > cats
내부에 파일이 정상적으로 생성이 된 것을 확인할 수 있다.
mp4, mp3 같은 파일도 전송이 가능하다.
서버에 정적파일 제공 로직
임포트
import * as path from 'path';
JavaScript
복사
일단 자동임포트가 될 테지만, 저 형태로 임포트 형태를 바꿔주자.
미들웨어 등록
app.useStaticAssets(path.join(__dirname, './common', 'uploads'), {
prefix: '/media',
});
JavaScript
복사
위의 코드를 추가해주면 된다. 하지만 useStaticAssets가 에러가 날 것이다.
이유는 위에 app = 에 정의된 NestFactory 내부에 useStaticAssets 라는 메소드가 없기 때문이다.
그래서 <> 제네릭 문법으로 <NestExpressApplication> 타입을 명시하면 해당 메소드를 사용할 수 있다.
NestExpressApplication 변경
이렇게 타입을 명시해주면 이 app은 확실하게 Express 앱이 될 것이고, 이 express를 통해서 static파일들을 제공하는 것이다. 그래서 useStaticAssets메소드가 정상작동 할 것이다.
useStaticAssets 작동 방식
http://localhost:8000/media/cats/aaa.png
JavaScript
복사
이 로직이 작동하는 방식은
위의 주소처럼 URL 요청이 온다고 가정할 때, 호스트를 제외하고 /media 로 요청이 들어오게 되면,
서버 내부의 common/uploads 폴더를 개방하여 정상적으로 데이터를 이용할 수 있게 한다.
url로 정적 데이터 요청하기
1.
컨트롤러에 정적데이터 요청 주소를 설정한다.
2.
반환데이터를 복사
3.
반환 받은 주소를 직접 요청해서 사진이 나오면 된다.
잘 작동한다.
서비스 만들기
cats.controller.ts
이제 컨트롤러 업로드 메소드에 서비스를 연결한다.
아직 uploadImg서비스는 구현 되지 않았다.
로그인한 사용자의 객체와 업로드 파일 (files) 를 파라메터로 전송할 것이다.
jwt인증 추가
jwt인증은 모듈화가 되어있으므로, 이렇게 @UseGuards(JwtAuthGuard) 로 손쉽게 추가가 가능하다.
현재 사용자 가져오기
현재 로그인한 사용자를 가져오기 위해서, 일전에 만들어 두었던 CustomDecorator를 사용한다.
@CurrentCat 어노 테이션으로 현재 사용자를 가져오고 서비스로
유저정보와 파일을 전달한다.
이미지 업로드
CatsService 내부에 작성
// 이미지 업로드
async uploadImg(cat: Cat, files: Express.Multer.File) {
const fileName = `cats/${files.filename}`;
console.log(fileName);
const newCat = await this.catsRepository.findByIdAndUpdateImg(
cat.id,
fileName,
);
console.log(newCat);
return newCat;
}
JavaScript
복사
디폴트 이미지 설정
우린 사용자가 가입할 때 사진을 업로드 하지 않으므로, 이미지를 올리면 update로직이 된다. 그래서 기본 회원사진이 필요하다.
1.
속성추가
https://raw.githubusercontent.com/amamov/teaching-nestjs-a-to-z/main/images/1.jpeg
JavaScript
복사
위 링크를 imgUrl기본 이미지로 추가한다. @Prop 데코레이터 사용
2.
readOnlyData 수정
readOnlyData에 imgUrl을 추가한다.
이제 currentCat 메소드를 반환할 때 이미지도 같이 반환할 수 있다.
catsRepository.findByIdAndUpdateImg
// Id로 고양이를 찾아서 img를 업데이트 하는 로직
async findByIdAndUpdateImg(id: string, fileName: string) {
const cat = await this.catModel.findById(id);
cat.imgUrl = `http://localhost:8000/media/${fileName}`;
const newCat = await cat.save();
console.log(newCat);
return newCat.readOnlyData;
}
JavaScript
복사
→ id 로 객체를 찾아서 imgUrl 항목을 업데이트 해주고, save까지 해야된다.
근데 이건 transactional이 없나???
폴더 정리
이렇게 repository / service / controller 따로 폴더를 만들어서 관리하기로 한다.
확인
기본설정 사진이 잘 작동한다.