공유 오피스를 등록하고 예약하는 프로젝트에서 회의실 등록을 위한 API를 구현하던 중 이미지와 데이터를 같이 전송하는 방법과 Nest 프레임워크에서 이 요청을 처리하는 방법에 대해 고민이 많았습니다. 구글에 어떤 키워드로 검색해야 할지 몰라 헤매다가 구름 트레이닝 강사님께 조언을 얻어 보고 제가 시도해 본 결과를 공유해 보겠습니다!
(이미지는 S3에 저장되고 경로만 DB에 저장)
- Nest.js - MVC와 Repository 패턴을 준수한 디렉토리(...)
- Postman
- VS Code
- S3 (S3에 이미지를 저장하는 방법은 추후에 포스팅하겠습니다.)
위 사진과 같이 공유 오피스를 등록하는 기능을 만드는 것이 목적입니다! ( 아직 프론트 미완성으로 Postman을 사용 )
npm i -D @types/multer
controller.ts
import { Body, Controller, Get, Post, UploadedFile, UseInterceptors } from '@nestjs/common';
import { CreateReservationDto } from '../dto/create-reservation.dto';
import { ReservationService } from '../services/reservation.service';
import { FileInterceptor } from '@nestjs/platform-express';
@Controller('reservation')
export class ReservationController {
constructor(
private readonly reservationService: ReservationService,
) { }
@Post('set-office')
@UseInterceptors(FileInterceptor('image'))
async create(@UploadedFile() file: Express.Multer.File, @Body() reservationDto: CreateReservationDto) {
return await this.reservationService.create('reservation', file, reservationDto);
}
}
주요 코드만 살펴보면
@UseInterceptors(FileInterceptor('image'))
FileInterceptor('file')
async create(@UploadedFile() file: Express.Multer.File, @Body() reservationDto: CreateReservationDto) {
return await this.reservationService.create('reservation', file, reservationDto);
}
this.reservationService.create('reservation', file, reservationDto);
서비스에 create함수로 넘기는 인자
file
reservationDto
service.ts
import { BadRequestException, Injectable } from '@nestjs/common';
import * as AWS from 'aws-sdk';
import * as path from 'path';
import { ReservationRepository } from '../repository/reservation.repository';
import { CreateReservationDto } from './../dto/create-reservation.dto';
@Injectable()
export class ReservationService {
private readonly awsS3: AWS.S3;
public readonly S3_BUCKET_NAME: string
constructor(
private readonly reservationRepository: ReservationRepository
) {
this.awsS3 = new AWS.S3({
accessKeyId: process.env.AWS_S3_ACCESS_KEY,
secretAccessKey: process.env.AWS_S3_SECRET_KEY,
region: process.env.AWS_S3_REGION,
});
this.S3_BUCKET_NAME = process.env.AWS_S3_BUCKET_NAME;
}
async create(folder: string, file: Express.Multer.File, reservationDto: CreateReservationDto) {
try {
const key = `${folder}/${Date.now()}_${path.basename(
file.originalname,
)}`.replace(/ /g, '');
await this.awsS3
.putObject({
Bucket: this.S3_BUCKET_NAME,
Key: key,
Body: file.buffer,
ACL: 'public-read',
ContentType: file.mimetype,
})
.promise();
const imgUrl = `https://${process.env.AWS_S3_BUCKET_NAME}.s3.amazonaws.com/${key}`;
const result = await this.reservationRepository.create(imgUrl, reservationDto);
return result;
} catch (error) {
throw new BadRequestException(`File upload failed : ${error}`);
}
}
}
이미지를 S3로 저장하는 코드는 생략하고 집중적으로 봐야 할 부분은 imgUrl과 reservationDto을 reservationRepository에 create로 인자를 전달하는 부분입니다.
const result = await this.reservationRepository.create(imgUrl, reservationDto);
imgUrl
reservationDto
repository
import { BadRequestException, Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { Reservation } from '../reservation.schema';
import { CreateReservationDto } from '../dto/create-reservation.dto';
@Injectable()
export class ReservationRepository {
constructor(
@InjectModel(Reservation.name) private readonly reservationModel: Model<Reservation>,
) { }
async create(imgUrl: string, reservationDto: CreateReservationDto) {
const { telNum, pay, placeName, decrition, areaName, detailAdress } = reservationDto
try {
await this.reservationModel.create({ telNum, pay, placeName, decrition, areaName, detailAdress, imgUrl });
return { result: true, message: "회의실 등록 성공" };
} catch (error) {
throw new BadRequestException(`File upload failed : ${error}`);
}
}
}
한 줄씩 살펴보겠습니다.
async create(imgUrl: string, reservationDto: CreateReservationDto) {
const { telNum, pay, placeName, decrition, areaName, detailAdress } = reservationDto
서비스에서 imgUrl과 reservationDto을 전달받아 reservationDto을 구조 분해 할당하고
try {
await this.reservationModel.create({ telNum, pay, placeName, decrition, areaName, detailAdress, imgUrl });
return { result: true, message: "회의실 등록 성공" };
} catch (error) {
throw new BadRequestException(`File upload failed : ${error}`);
}
분해된 객체 데이터와 imgUrl을 create 메소드를 이용해 생성해주면 끝입니다!
(office.jpeg)
회의실 사진을 데이터와 함께 form-data 객체에 담아 서버로 요청을 보냅니다.
포스트맨을 확인해보면 정상적인 응답을 받았고
DB에서도 정상적으로 데이터가 저장됐습니다.
이미지 경로도 정상적으로 접속돼 확인이 가능합니다 ( 이상해 보이지만 정상입니다.. )
프로필 사진 기능 구현을 위해 이미지만 업로드는 해봤지만 데이터와 이미지를 넘기는 방법은 처음 사용해 봤기 때문에 포스팅하기에 좋은 주제라고 생각해 업로드하게 되었습니다. 구글에 어떤 키워드로 검색을 해야 할지 몰라서 그런지는 몰라도 원하는 검색 결과가 나오지는 않았기 때문에 같은 고민을 하는 분들에게 도움이 되었으면 좋겠습니다.
(도움을 주신 존 안 강사님 감사합니다..)
야간 수업이 끝나고.. 1시가 조금 넘었지만 오늘은 꼭 포스팅을 하겠다고 다짐했기 때문에 졸음을 참으며 글을 작성하고 있습니다. 포스팅을 할 때마다 느끼는 건 생각보다 뿌듯하고 보람차다 앞으로도 모르는 내용이나 기록할 만한 내용이 생긴다면 또 포스팅을 하겠다고 다짐하며 내일 부캠을 위해 글을 마치겠습니다!
await this.reservationModel.create( telNum, pay, placeName, decrition, areaName, detailAdress, imgUrl);