일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- Bull
- nestjs
- Nest.js
- GIT
- Python
- Dinosaur
- nodejs
- flask
- MySQL
- Queue
- cookie
- OCR
- react
- MongoDB
- 게임
- AWS
- 공룡게임
- 자료구조
- Sequelize
- Express
- 정렬
- game
- typeORM
- class
- JavaScript
- dfs
- mongoose
- jest
- TypeScript
- Today
- Total
포시코딩
[Nest.js] FormData 전달받기 with multer 본문
개요
string, number type의 데이터와 이미지 파일이 같이 담긴 FormData에 대해
Nest.js 상에서 어떻게 해야
multer를 통해 이미지 파일을 받고
나머지 데이터들에 대해 DTO를 적용해 type을 가려 받을 수 있는지에 대해
내 시행착오를 정리해보았다.
발단
Client Server
const formData = new FormData();
formData.append('title', title);
formData.append('content', content);
formData.append('writerId', writerId);
formData.append('joinLimit', joinLimit);
formData.append('file', file);
axios.post('http://localhost:8080/boards', formData)
// ...생략
이런 형태로 Nest.js 서버에 POST 요청하는 코드가 있었는데
Controller
@Post()
createBoard(
@Body() boardData,
) {
console.log('boardData: ');
console.log(boardData);
return;
}
처음에는 이런 형태로 받으려고 하니
아예 제대로 받지 못하는 상황이 생겼다.
시행착오
nestjs-form-data
일단 FormData를 받는 게 우선순위였기에
먼저 Nest.js에서 FormData를 정상적으로 받아 위에서 나온 {}가 아닌
Object 형태로 받게끔 하는 방법을 찾아보았다.
그 과정에서 nestjs-form-data 라는 패키지를 발견해 사용해 보았다.
사용과정 하단 참고
사용 결과
사용 결과 겉보기에는 아주 성공적이었다.
Controller
@Post()
@FormDataRequest()
createBoard(
@Body() boardData: CreateBoardDto,
) {
console.log('boardData: ');
console.log(boardData);
return;
}
다만 위처럼 Body에 DTO를 type으로 적용 시 받지 못하는 문제가 생겼고
DTO에 file 타입을 넣기도 애매하고 추후 붙이게 될 S3라던지
multer를 사용해 활용하고 싶은 마음이 컸다.
그래서 일단 nestjs-form-data 패키지는 아쉽지만 안 쓰는 방향으로 생각해 보기로 했다.
multer 사용
코드
multer.options.factory.ts
import { MulterOptions } from '@nestjs/platform-express/multer/interfaces/multer-options.interface';
import { memoryStorage } from 'multer';
export const multerOptionsFactory = (): MulterOptions => {
return {
storage: memoryStorage(),
limits: { fileSize: 10 * 1024 * 1024 }, // 10MB로 크기를 제한
};
};
Module
// ...import 생략
@Module({
imports: [
MulterModule.registerAsync({
useFactory: multerOptionsFactory,
}),
],
controllers: [BoardsController],
providers: [BoardsService]
})
export class BoardsModule {}
Controller
@Post()
@UseInterceptors(FileInterceptor('file'))
createBoard(
@Body() boardData,
@UploadedFile() file: Express.Multer.File,
) {
console.log('boardData: ');
console.log(boardData);
console.log('file: ');
console.log(file);
return;
}
사용 결과
사용 결과는 대성공이었다.
해당 결과까지의 과정이 순탄치는 않았지만 정리할 거리도 없기에 생략
아마 multer 사용 방법이 다른 블로그 글들과 좀 다를 텐데
service에 데이터를 넘겨 저장하다 실패가 나도 이미지는 multer로 인해 저장 상태 그대로 남게 되는 경우가 있어
아예 service에서 성공적으로 데이터 저장 시 이미지를 저장하게끔 하기 위해 이렇게 만들었다.
하지만 문제가 없는 건 아니었다.
writerId, joinLimit은 number type으로 받아야 되는 값들인데
FormData에 담겨 넘어와서 그런지 string으로 변환되어 있었다.
위 코드는 일단 multer를 사용했을 때 데이터가 잘 넘어오는지 확인하느라
boardData에 대해 dto를 type으로 지정해놓지 않은 상태였고
지정하면 타입이 맞지 않아 튕겨내고 있는 상황.
class-transformer를 사용하고 있었지만 FormData 내부의 값들까지 변환해주지는 못하는 모양이었다.
여기서 또 한참을 헤매고 있었다.
시행착오 2
CreateBoardDto
// ...import 생략
export class CreateBoardDto {
@IsString()
readonly title: string;
@IsString()
readonly content: string;
@IsNumber()
readonly writerId: number;
@IsNumber()
readonly joinLimit: number;
}
원래 사용하고 있던 dto의 코드다.
controller에서 해당 dto를 적용하면 writerId, joinLimit은 number여야 하기 때문에 튕겨내는데
https://github.com/typestack/class-validator/blob/develop/README.md#validation-decorators
공식 문서에서 @IsNumber() 대신
문자열의 형태가 숫자인지 확인하는 @IsNumberString()을 사용하면 될 것 같다는 힌트를 얻었고
적용 결과
controller에서 dto를 적용해도 데이터를 잘 넘겨받을 수 있었다.
이제 넘겨받긴 했으니 boardData를 가공해 쓰면 되는 일이었다.
문제 발생
하지만 여기서 두 가지 문제가 발생했는데
- writerId는 dto에서 readonly로 선언됐기 때문에 변경할 수 없다.
- @IsNumberString()의 영향인지 wrtierId는 number로 취급받고 있음
2번은 내가 번역을 잘못한 거일 수도 있는 게 boardData를 출력해 보면 여전히 string 형태로 나온다.
어쨌든 @IsNumberString()으로도 제대로 해결할 수 없는 상황임이 분명했다.
해결 방법
이번엔 @IsNumberString()을 키워드 삼아 또 열심히 구글링 했는데
다행스럽게도 GitHub nestjs의 issue에 비슷한 고민에 대해 질문한 사람이 있어
답을 얻을 수 있었다.
https://github.com/nestjs/nest/issues/1331
dto
// ...import 생략
export class CreateBoardDto {
@IsString()
readonly title: string;
@IsString()
readonly content: string;
@Type(() => Number)
@IsNumber()
readonly writerId: number;
@Type(() => Number)
@IsNumber()
readonly joinLimit: number;
}
@IsNumberString() 대신 @Type()을 통해 타입 변환을 해준 뒤 @IsNumber()를 쓰는 방법이었는데
적용 결과
controller에서 원하는 형태로 데이터를 받는 모습을 확인할 수 있었다.
정리
express에선 아무 생각 없이 받아 썼던 부분들이
TypeScript의 사용으로 복잡해지고 어려워진 부분이 없지 않았지만
이런 과정 덕분에 Back-End로써 좀 더 깐깐하게 요청받는 데이터들에 대해 확인할 수 있다고 생각한다.
그리고 처음 배울 땐 dto에서 @IsString(), @IsNumber() 말고는 안 알려줘 이 두 개만 써왔는데
이렇게 직접 부딪히며 필요성을 느끼면서 배우니 금방 잊지도 않을 거 같고 성취감도 느낄 수 있었다.
이제 얻은 boardData와 file에 대해서 service로 넘겨
해당 게시글을 저장하는 일이 남았는데
이 포스팅의 주제를 벗어나는 부분이라 여기까지 하고 마치겠다.
multer 사용 관련은 아래 팀원의 코드에서 도움을 받았다.
'Node.js' 카테고리의 다른 글
[Nest.js] 원하는 시점에 AWS S3에 파일 저장 (0) | 2023.02.23 |
---|---|
[Nest.js] 캐싱 사용해보기 (0) | 2023.02.22 |
[동시성 문제] Apache JMeter를 이용한 테스트 (0) | 2023.02.20 |
[Nest.js][동시성 문제] Bull Queue (0) | 2023.02.18 |
[Nest.js][동시성 문제] Transaction 사용과 통 Lock 걸어버리기 (0) | 2023.02.17 |