NestJs+GraphQL File Upload

Harry·2021년 5월 14일
1

NestJs

목록 보기
2/2
post-custom-banner

File Upload using GraphQL

  • (NestJS) graphql-upload 말고 apollo-server-express 에서 제공해주는 GraphQLUpload type을 사용할 것
  • Gql Type은 GraphQLUpload 를 사용하고, Typescript 에서 사용할 type 은 직접 만들어줄 것
interface FileUpload {
  filename: string;
  mimetype: string;
  encoding: string;
  createReadStream(): ReadStream;
}
  • aws 를 통해 upload할 경우, Body: createReadStream() 를 넣어주면 되지만 Maximum Call Stack 에러가 날 경우, node 버전을 12 로 바꿀 것
  • 링크 를 통해 들어가보면, 에러가 아직 안고쳐져있다는 걸 확인할 수 있음 ( github issue 들어가보길 __ 2021년 5월 14일 글 작성 기준 )

예시 코드

  • Resolver 쪽에다 넣으시면 됩니다.
@Mutation(() => Boolean)
async uploadFile(
  @Args('file', { type: () => GraphQLUpload }) file: FileUpload,
): Promise<boolean> {
  const result = await this._awsService.uploadToS3({
    Key: 'fdjksfljs',
    ContentEncoding: file.encoding,
    Body: file.createReadStream(),
    ContentType: file.mimetype,
  });
  console.log(result);
  return true;
}

요청보내기

  • uploadFile → Resolver 이름
  • 0=@/Users~ → 파일이 있는 경로
curl -v localhost:4000/graphql \
  -F operations='{"query":"mutation T($file:Upload!) {uploadFile(file:$file)}", "variables":{"file":null}}'\
  -F map='{ "0": ["variables.file"] }' \
  -F 0=@/Users/hanjaenam/Archive/2021-plan/2_project/ec-server/stone.jpg

Gql Interceptor 만드는 방법 ( NestJs 공식 사이트에서 제공해주는 Multer 를 활용한 FileInterceptor 와 비슷하게 )

NestJS upload using GraphQL

내 방식대로 작성한 코드

import {
  CallHandler,
  ExecutionContext,
  Injectable,
  mixin,
  NestInterceptor,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { GqlCtx } from '~/@graphql/graphql.interface';
import { AwsService } from '~/aws/aws.service';
import { FileUploadInput } from '../dto/fileUpload.dto';

export function GqlFileInterceptor(
  loc: 'user' | 'post',
  fieldNameList: string[],
) {
  @Injectable()
  class MixinInterceptor implements NestInterceptor {
    constructor(private readonly _awsService: AwsService) {}

    async upload(fieldName: string, gqlCtx: GqlExecutionContext) {
      const { uploadedFiles } = gqlCtx.getContext().res.locals;
      const files = await gqlCtx.getArgs().input[fieldName];
      return Promise.all(
        (Array.isArray(files) ? files : [files]).map(
          async ({
            mimetype,
            encoding,
            createReadStream,
            filename,
          }: FileUploadInput) => {
            const result = await this._awsService.uploadToS3({
              ContentType: mimetype,
              ContentEncoding: encoding,
              Body: createReadStream(),
              Key: `${loc}/${new Date().valueOf()}_${filename
                .split('.')
                .slice(0, -1)
                .join('.')}`,
              ACL: 'public-read',
            });
            if (!uploadedFiles[fieldName]) {
              uploadedFiles[fieldName] = [result.Location];
            } else {
              uploadedFiles[fieldName].push(result.Location);
            }
          },
        ),
      );
    }

    async intercept(context: ExecutionContext, next: CallHandler<any>) {
      const gqlCtx = GqlExecutionContext.create(context);
      const existFieldNameInArgs = fieldNameList.some((fieldName) =>
        Object.keys(gqlCtx.getArgs().input).includes(fieldName),
      );
      if (existFieldNameInArgs) {
        (<GqlCtx['res']>gqlCtx.getContext().res).locals = {
          uploadedFiles: {},
        };
        await Promise.all(
          fieldNameList.map((fieldName) => this.upload(fieldName, gqlCtx)),
        );
      }
      return next.handle();
    }
  }
  return mixin(MixinInterceptor);
}
post-custom-banner

0개의 댓글