본문 바로가기
개발 Study/GraphQL

[GraphQL/Typescript] GraphQL로 이미지 파일 업로드하기

by jiyoon_92 2022. 12. 14.
반응형

GraphQL로 파일 업로드하기

GraphQL

 

1. grpahql-upload 설치하기

Graphql로 파일 업로드를 하기 위해선 Upload라는 Scalar Type을 다룰 수 있게 돕는 graphql-upload 을 npm으로 설치해야한다. 그러나 Typescript를 쓰는 분들은 아래와 같은 에러를 많이 접할 것이고 고치고 쓰는 분들도 있겠지만 나는 버전 충돌과 현재 설치된 다른 모듈들에도 영향을 주기 때문에 grpahql-upload-ts를 설치했다. 아래 두 가지 경우에 대해 링크를 첨부하니 본인에게 맞는 방법으로 설치해보자!

1-1. graphql-upload-ts 설치

https://www.npmjs.com/package/graphql-upload-ts?activeTab=readme#type-fileupload

 

1-2 grpahql-upload js 설정 방법

https://stackoverflow.com/questions/72361047/error-no-exports-main-defined-in-graphql-upload-package-json

 

Error: No "exports" main defined in graphql-upload/package.json

Have installed graphql-upload, do import { graphqlUploadExpress } from 'graphql-upload'; And getting this error: Error: No "exports" main defined in graphql-upload/package.json Dependenci...

stackoverflow.com

 


2. Resolver에 Upload Scalar Type추가하기

GraphQLUpload를 resolver에 추가해준다. 추가되었는지 확인하려면 console.log(resolvers)를 해보면 알 수 있다.

import { loadSchema } from '@graphql-tools/load';
import { GraphQLFileLoader } from '@graphql-tools/graphql-file-loader';
import { makeExecutableSchema } from '@graphql-tools/schema';
import resolverMap from '../resolver';
import { GraphQLSchema } from 'graphql';
import { GraphQLUpload } from "graphql-upload-ts";

export default async function loadAllSchema() : Promise<GraphQLSchema> {
    const typeDefs = await loadSchema('src/**/*.graphql', {
        loaders: [new GraphQLFileLoader()]
    });

    var resolvers = resolverMap;
    if (resolvers && !resolvers.Upload) {
        resolvers.Upload = GraphQLUpload;
    }
    const schema: GraphQLSchema = makeExecutableSchema({
        typeDefs : typeDefs,
        resolvers,
    });
    return schema;
}
반응형

3. Schema에 Type 추가 및 Mutation에 추가하기

Upload를 scalar type으로 추가하고 복수로 주고받을 수 있도록 input type도 추가해놓는다. 그리고 사이트에서 제공한 File Type도 추가했다.

https://www.apollographql.com/docs/apollo-server/v2/data/file-uploads/

 

File uploads

Enabling file uploads in Apollo Server

www.apollographql.com

scalar Upload

input UploadFileListInput {
    fileList: [Upload!]!
}

type File {
    filename: String
    mimetype: String
    encoding: String
}

 


4. Image 다루는 Controller/Resolver 추가하기

createReadStream으로 이미지 데이터를 받은 후 읽어들여서 Buffer에 담아 binary형태로 DB에 저장했다. 본인은 mssql을 이용했기 때문에 varbinary(max) type으로 8MB 까지 처리가 가능하다. FileUpload라는 타입을 통해 파일을 받을 수있는 createReadStream, 파일명인 filename, 파일 타입인 mimetype으로 파일 정보를 주고받을 수 있다.

/**
   * 이미지 등록하기
   * @param args 새 정보
   * @returns 성공 시 True, 새 이미지 정보. 실패 시 False
   */
  const insertImageItem = async (args : any) => {
    const { createReadStream, filename, mimetype } = await args.file as FileUpload;
    await new Promise(async (resolve, reject) => {
      const readStream = createReadStream();
      var bufs : Uint8Array[] = [];
      readStream.on('data', (chunk : Uint8Array) => {
        bufs.push(chunk);
    });
    
    // 파일 읽기 완료 이벤트 리스너 사용 : reatream.on('end')
    readStream.on('end', async () => {
        const newImage : ImageDto = {
            member_id: args.member_id,
            image_type: args.image_type,
            file: Buffer.concat(bufs),
        }
      try {
        const image : Image = await Image.create(newImage);
        return {res : true, msg : `Created image ${image.id}.`, data : [image]};
      } catch(error) {
        return {res : false, msg : error};
      }
    });
    
    // 에러 발생 시 이벤트 리스너 사용 : reatream.on('error') 
    readStream.on('error', (err : string) => {
        console.log('error:', err);
    });
    });
  }

작성된 Controller를 아래와 같이 resolver랑 연결시키고 mutation에 등록시키면 끝난다.

const createImage = async (_: void, args:any) => {
    return await Image.insertImageItem(args);
};

Schema에 연결

  type Mutation {
  #Image
  createImage(member_id: Int! image_type: String! file: Upload!): ImageResponse!
  updateImage(id:Int! image_type:String file:Upload): ImageResponse!
  }

이번 삽질은 원하는 바를 얻기까지 좀 오래걸렸는데 다음에 대용량으로 파일을 주고받을때 stream을 어떤식으로 이용할지 옵션을 잘 보고 짜야겠다.

생각보다 file upload에 대한 정보가 많이 없어서 ㅠ 나와 같은 고민을 하신 분들께 많은 도움이 되었으면 좋겠다.

반응형

댓글