1)

npm i -D @types/multer

cats.controller.ts

import { FileInterceptor, FilesInterceptor } from '@nestjs/platform-express';
...
  @ApiOperation({ summary: '이미지 업로드를 위한 API입니다.' })
  @UseInterceptors(FilesInterceptor('image', 10, multerOptions('cats')))
  @UseGuards(JwtAuthGuard)
  @Post('upload')
  uploadCatImage(@UploadedFiles() files: Array<Express.Multer.File>, @CurrentUser() cat: Cat) {
    return this.catsService.uploadImg(cat, files);
  }

cats.module.ts

import { MulterModule } from '@nestjs/platform-express';
...
@Module({
  imports: [
    MulterModule.register({
      dest: './upload',
    }),

binary data는 content-type을 multipart/form 형식으로 보내야 한다. 이를 Multer package가 담당한다.

cats.controller에 단일 파일/다중 파일 upload를 위한 장식자를 설정해주고 module을 등록해주었다. 이제 프론트엔드에서 upload를 진행하면 최상위 경로에 upload 폴더가 생성될 것이다. 하지만 담기는 파일은 아직 우리가 생각하는 이미지나 mp4 파일이 아니다.

경로를 설정하는 multerOptions은 가독성을 위해서 common/utils에 생성되게 설정했다. cats나 users 같이 도메인별로 서로 다른 업로드 폴더를 사용할 것이다. 이제 dist의 common에는 우리가 해석할 수 있는 파일이 담긴다.

2)

main.ts

async function bootstrap() {
  const app = await NestFactory.create<NestExpressApplication>(AppModule);
...
  app.useStaticAssets(path.join(__dirname, './common', 'uploads'), {
    prefix: '/media',
  });

반대로 서버에서 프론트로 파일을 serving할 때는 파일이 저장된 경로를 전달한다고 한다. useStaticAssets() method는 제네릭으로 NestExpressApplication임을 명시해야 사용 가능하다.

/media prefix를 넣어줌으로써 localhost:3000/media/cats/aaa.png 같은 형태로 서빙이 가능하다.

3)

comments.service.ts

@Injectable()
export class CommentsService {
  constructor(
    @InjectModel(Comments.name) private readonly commentsModel: Model<Comments>,
    private readonly catsRepository: CatsRepository,
  ) {}

의존성을 주입할 때 class가 아닌 model은 @InjectModel을 사용함을 주의하자.

vs code 상에서는 더 확인이 어려운데 소스코드에서는 getModelToken() method가 정의되어있는 것으로 보인다.

https://github.com/nestjs/mongoose/blob/c43510e515260924def21fc41aff69d0524c1e7f/lib/common/mongoose.utils.ts#L6 참조

4)

cats.module.ts

@Module({
  imports: [
    MulterModule.register({
      dest: './upload',
    }),
    MongooseModule.forFeature([
      { name: Comments.name, schema: CommentsSchema },
      { name: Cat.name, schema: CatSchema },
    ]),
    forwardRef(() => AuthModule),
  ],

cats.schema.ts

  readonly readOnlyData: {
    id: string;
    email: string;
    name: string;
    imgUrl: string;
    comments: Comments[];
  };

  readonly comments: Comments[];
}

export const _CatSchema = SchemaFactory.createForClass(Cat);

_CatSchema.virtual('readOnlyData').get(function (this: Cat) {
  return {
    id: this.id,
    email: this.email,
    name: this.name,
    imgUrl: this.imgUrl,
    comments: this.comments,
  };
});

_CatSchema.virtual('comments', {
  ref: 'comments',
  localField: '_id',
  foreignField: 'info',
});

_CatSchema.set('toObject', { virtuals: true });
_CatSchema.set('toJSON', { virtuals: true });

export const CatSchema = _CatSchema;

cats.repository.ts

  async findAll() {
    const CommentsModel = mongoose.model('comments', CommentsSchema);
    const result = await this.catModel
      .find()
      .populate('comments', CommentsModel);
    return result;
  }

cats.service.ts

  async getAllCat() {
    const allCat = await this.catsRepository.findAll();
    const readOnlyCats = allCat.map((cat) => cat.readOnlyData);
    return readOnlyCats;
  }

populate를 사용하는 과정에서 cats에서 comments를 사용하므로 module과 schema에 변화가 생겼다. 먼저 cats.module에서 comments의 schema와 name을 가져온다.

그리고 언더바를 추가하면서 기존 readOnlyData virtual 영역에 commets virtual 영역을 정의한다. 이때 외래키를 info로 넣어주면 해당 게시글에 대한 comments를 볼 수 있을 것이다. author 역시 ObejctId를 사용하므로 이를 이용한다면 한 유저가 단 댓글을 모두 볼 수도 있을 것이다.

repository에서는 Express.js에서 model을 사용한 용법이 나왔다. populate를 사용하기 가장 좋은 방법은 schema의 virtual field와 모델명을 명시적으로 적어주는 것으로 보인다.

0개의 댓글