선 업로드 방식이란
- 이미지를 선택할때마다 이미지는 먼저 업로드된다.
- 업로드된 이미지들은 '임시' 폴더에 잠시 저장해둔다. (/public/temp)
- 이미지 업로드를 한 후 응답받은 이미지의 경로만 저장해둔 후 포스트를 업로드할 때 이미지의 경로만 추가해준다.
- POST /posts 엔드포인트에 이미지 경로를 함께 보낼 경우 해당 이미지를 임시폴더(/public/temp)에서 포스트폴더(/public/posts)로 이동시킨다.
- PostsModel의 image 필드에 경로를 추가해준다.
- S3 presigned url을 사용하면 많이 사용되는 방식이다.
기존 방식 | 신규 방식(선 업로드) | |
체감 속도 | 모든 이미지가 업로드되는 시간을 사용자가 처음부터 끝까지 느껴야하므로 사용자는 서비스가 느리다고 생각하게 된다. | 이미지를 선택하는 순간 서버에 업로드가 진행되기 때문에 사용자는 서비스가 빠르다고 느낌 |
서버 과부하 | 포스팅(업로드) 버튼을 누를 때만 요청이 보내지기 때문에 과부하가 적음 | 이미지 선택마다 요청이 보내지기 때문에 과부하가 많음 |
엔드포인트 관리 | 파일을 업로드하는 엔드포인트가 생길때마다 업로드관련 multer를 세팅해줘야한다. | 공통된 이미지 업로드 엔드포인트 하나를 만들어서 모든 이미지 업로드를 관리한다. |
파일 관리 | 포스팅될 때의 이미지들만 업로드되기 때문에 잉여파일 생길 가능성이 적다. | 이미지를 선택하면 임시폴더가 생성되고 업로드되기 때문에 잉여파일이 생성될 수 있다. |
common.module.ts 로 multer 세팅 옮기기
import { BadRequestException, Module } from '@nestjs/common';
import { CommonService } from './common.service';
import { CommonController } from './common.controller';
import { MulterModule } from '@nestjs/platform-express';
import { extname } from 'path';
import * as multer from 'multer';
import {v4 as uuid} from 'uuid';
import { existsSync, mkdirSync } from 'fs';
import { TEMP_FOLDER_PATH } from './const/path.const';
import { AuthModule } from 'src/auth/auth.module';
import { UsersModule } from 'src/users/users.module';
// 업로드 경로가 존재하지 않으면 생성 (recursive 옵션으로 상위 폴더까지 생성)
if (!existsSync(TEMP_FOLDER_PATH)) {
mkdirSync(TEMP_FOLDER_PATH, { recursive: true });
}
@Module({
imports: [
MulterModule.register({
limits: {
// 바이트 단위로 입력
fileSize: 1024 * 1024 * 10,
},
fileFilter: (req, file, cb) => {
/**
* cb(에러, boolean)
*
* 첫 번째 파라미터에는 에러가 있을 경우 에러 정보를 넣어준다.
* 두 번째 파라미터는 파일을 받을지 말지 boolean을 넣어준다.
*/
// xxx.jpg -> jpg(확장자)만 가져옴
const ext = extname(file.originalname);
if(ext !== '.jpg' && ext !== '.jpeg' && ext !== '.png') {
return cb(
new BadRequestException('jpg/jpeg/png 파일만 업로드 가능합니다.'),
false,
);
}
return cb(null, true);
},
storage: multer.diskStorage({
destination: function(req, res, cb) {
cb(null, TEMP_FOLDER_PATH);
},
filename: function(req, file, cb) {
cb(null, `${uuid()}${extname(file.originalname)}`)
}
}),
}),
AuthModule,
UsersModule,
],
controllers: [CommonController],
providers: [CommonService],
exports: [CommonService],
})
export class CommonModule {}
기존 posts.module.ts 에 있던 multer 세팅을 common.module.ts 의 imports로 이동한다. 추가로 AccessGuard를 사용하여야 하기 때문에 AuthModule과 UsersModule도 같이 imports에 넣어준다.
common.controller.ts 에 image 업로드하는 Controller 만들기
import { Controller, Post, UploadedFile, UseGuards, UseInterceptors } from '@nestjs/common';
import { CommonService } from './common.service';
import { FileInterceptor } from '@nestjs/platform-express';
import { AccessTokenGuard } from 'src/auth/guard/bearer-token.guard';
@Controller('common')
export class CommonController {
constructor(private readonly commonService: CommonService) {}
@Post('image')
@UseGuards(AccessTokenGuard)
@UseInterceptors(FileInterceptor('image'))
postImage(
@UploadedFile() file: Express.Multer.File,
) {
return {
fileName: file.filename,
}
}
}
위의 컨트롤러를 Postman에서 요청을 보내면 잘 실행되는 것을 볼 수 있다.
POST posts 엔드포인트 변경하기
dto에서 image 경로를 받고, /public/temp에 있는 이미지를 /public/posts 로 옮기는 과정이 필요하다.
CreatePostDto 수정하기
import { PickType } from "@nestjs/mapped-types";
import { PostsModel } from "../entities/posts.entity";
import { IsOptional, IsString } from "class-validator";
export class CreatePostDto extends PickType(PostsModel, ['title', 'content']) {
@IsString()
@IsOptional()
image?: string;
}
엔티티가 생성될때 임시 폴더로부터 이미지 파일 이동시키기
async createPostImage(dto: CreatePostDto) {
// dto의 이미지 이름을 기반으로 파일의 경로를 생성한다.
const tempFilePath = join(
TEMP_FOLDER_PATH,
dto.image!,
);
try {
// 파일이 존재하는지 확인. 파일이 없는 경우 에러발생.
await promises.access(tempFilePath);
} catch (error) {
throw new BadRequestException(`${error}: 존재하지 않는 파일입니다.`);
}
// 파일 이름만 가져오기
const fileName = basename(tempFilePath);
// 새로 이동할 포스트 폴더 이름 + 이미지 이름
const newPath = join(
POST_IMAGE_PATH,
fileName,
);
// 파일 옮기기
await promises.rename(tempFilePath, newPath);
return true;
}
createPostImage는 클라이언트로부터 받은 이미지가 있는지 검사하고, 있는 경우 파일을 옮기는 역할을 진행한다.
postPosts 수정하기
@Post()
@UseGuards(AccessTokenGuard)
async postPosts(
@User('id') userId: number,
@Body() body: CreatePostDto,
// @Body('title') title: string,
// @Body('content') content: string,
) {
await this.postsService.createPostImage(body);
return this.postsService.createPost(userId, body);
}
포스트를 호출하는 컨트롤러에서 createPostImage를 먼저 호출하면 파일을 다 옮기고 createPost를 호출하게 된다.
'NestJS' 카테고리의 다른 글
[NestJS] Interceptor (0) | 2025.03.10 |
---|---|
[NestJS] Transaction (0) | 2025.03.10 |
[NestJS] 파일 업로드 (클래식 방식) (0) | 2025.03.07 |
[NestJS] Config 모듈 사용하기 (0) | 2025.03.05 |
NestJS - Pagination 심화 (일반화하기) (0) | 2025.03.05 |