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일 글 작성 기준 )
예시 코드
@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);
}