Загрузка NestJS с использованием GraphQL

У кого-нибудь есть пример того, как загрузить файл в NestJs с помощью GraphQl?

Я могу загрузить, используя данный пример через контроллер

https://github.com/nestjs/nest/issues/262,

но я не смог найти исчерпывающую документацию по загрузке с помощью GrahpQL в NestJS

7 ответов

Apollo Server 2.0 должен иметь возможность сделать это сейчас (упакован в гнездо), хотя мне нужно было установить graphql-upload и импорт GraphQLUpload поскольку я не мог найти Upload тип:

@Mutation(() => Image, { nullable: true })
async addImage(@Args({ name: 'image', type: () => GraphQLUpload }) image) {
  // Do stuff with image...
}

На момент этого ответа FileInterceptor использует multer и путем преобразования ExecutionContext в http оно использует getRequest а также getResponse методы для обеспечения req а также res в multer.single которые они есть (req а также res) не определено в GraphQL.

Я попытался получить запрос из контекста, используя:

const ctx = GqlExecutionContext.create(context);

и есть req недвижимость в ctx но я не могу найти способ использовать multer (еще).

Во всяком случае, я внес некоторые изменения в FileFieldsInterceptor использовать его в моем проекте, но я могу сделать запрос на удаление, когда у меня будет время, чтобы очистить его:

import { Observable } from 'rxjs';
import {
  NestInterceptor,
  Optional,
  ExecutionContext,
  mixin,
} from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { storeFile } from './storeFile';

interface IField {
  name: string;
  options?: any;
}

export function GraphqlFileFieldsInterceptor(
  uploadFields: IField[],
  localOptions?: any,
) {
  class MixinInterceptor implements NestInterceptor {
    options: any = {};
    constructor(@Optional() options: any = {}) {
      this.options = { ...options, ...localOptions };
    }

    async intercept(
      context: ExecutionContext,
      call$: Observable<any>,
    ): Promise<Observable<any>> {
      const ctx = GqlExecutionContext.create(context);
      const args = ctx.getArgs();

      let storeFilesResult = await Promise.all(
        uploadFields.map(uploadField => {
          const file = args[uploadField.name];
          return storeFile(file, {
            ...uploadField.options,
            ...this.options,
          }).then(address => {
            args[uploadField.name] = address;
            return address;
          });
        }),
      );

      return call$;
    }
  }
  const Interceptor = mixin(MixinInterceptor);
  return Interceptor;
}

и файл-хранилище выглядит примерно так (нельзя использовать так):

import uuid from 'uuid/v4';
import fs from 'fs';
import path from 'path';

const dir = './files';
if (!fs.existsSync(dir)) {
  fs.mkdirSync(dir);
}

export const storeFile = async (file, options): Promise<any> => {
  // options is not doing anything right now
  const { stream } = await file;
  const filename = uuid();

  const fileAddress = path.join(dir, filename + '.jpg');
  return new Promise((resolve, reject) =>
    stream
      .on('error', error => {
        if (stream.truncated)
          // Delete the truncated file
          fs.unlinkSync(fileAddress);
        reject(error);
      })
      .pipe(fs.createWriteStream(fileAddress))
      .on('error', error => reject(error))
      .on('finish', () => resolve(fileAddress)),
  );
};

В моем Cats.resolvers.ts:

...
  @Mutation()
  @UseInterceptors(
    GraphqlFileFieldsInterceptor([
      { name: 'catImage1' },
      { name: 'catImage2' },
      { name: 'catImage3' },
    ]),
  )
  async cats(
    @Args('catImage1') catImage1: string,
    @Args('catImage2') catImage2: string,
    @Args('catImage3') catImage3: string,
  ){
    console.log(catImage1) // will print catImage1 address
    ...

Эта реализация отлично работает с Node> = v14.

  1. package.json

Удалите записи fs-конденсатор и graphql-upload из раздела разрешений, если вы их добавляли, и установите последнюю версию пакета graphql-upload (на данный момент v11.0.0) в качестве зависимости.

  1. src / app.module.ts

Отключите встроенную обработку загрузки Apollo Server и добавьте промежуточное ПО graphqlUploadExpress в свое приложение.

      import { graphqlUploadExpress } from "graphql-upload"
import { MiddlewareConsumer, Module, NestModule } from "@nestjs/common"

@Module({
  imports: [
    GraphQLModule.forRoot({
      uploads: false, // disable built-in upload handling
    }),
  ],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(graphqlUploadExpress()).forRoutes("graphql")
  }
}
  1. src / blog / post.resolver.ts (пример преобразователя)

Удалите импорт GraphQLUpload из apollo-server-core и вместо этого импортируйте из graphql-upload

      import { FileUpload, GraphQLUpload } from "graphql-upload"

@Mutation(() => Post)
async postCreate(
  @Args("title") title: string,
  @Args("body") body: string,
  @Args("attachment", { type: () => GraphQLUpload }) attachment: Promise<FileUpload>,
) {
  const { filename, mimetype, encoding, createReadStream } = await attachment
  console.log("attachment:", filename, mimetype, encoding)

  const stream = createReadStream()
  stream.on("data", (chunk: Buffer) => /* do stuff with data here */)
}

Источник: https://github.com/nestjs/graphql/issues/901#issuecomment-780007582

Некоторые другие ссылки, которые я нашел полезными:

Попробуй это

import { Resolver, Mutation, Args } from '@nestjs/graphql';
import { createWriteStream } from 'fs';

import {GraphQLUpload} from "apollo-server-express"

@Resolver('Download')
export class DownloadResolver {
    @Mutation(() => Boolean)
    async uploadFile(@Args({name: 'file', type: () => GraphQLUpload})
    {
        createReadStream,
        filename
    }): Promise<boolean> {
        return new Promise(async (resolve, reject) => 
            createReadStream()
                .pipe(createWriteStream(`./uploads/${filename}`))
                .on('finish', () => resolve(true))
                .on('error', () => reject(false))
        );
    }
    
}

РЕДАКТИРОВАТЬ: Согласно комментарию Developia ниже, apollo-server теперь осуществляет загрузку файлов. Должен быть предпочтительным способом.

Ниже оригинальный ответ, для справки.

Обычно для загрузки не используется GraphQL. GraphQL - это модная "спецификация API", означающая, что в конце дня низкоуровневый HTTP-запрос и ответы транслируются в / из объектов JSON (если у вас нет пользовательского транспорта).

Одним из решений может быть определение специальной конечной точки в схеме GraphQL, например:

mutation Mutation {
  uploadFile(base64: String): Int
}

Затем клиент преобразует двоичные данные в строку base64, что будет обрабатываться соответствующим образом на стороне распознавателя. Таким образом, файл станет частью объекта JSON, которым обмениваются клиент и сервер GraphQL.

Хотя это может подходить для небольших файлов, небольшого количества операций, это определенно не решение для службы загрузки.

Вам необходимо определить контроллер загрузки и добавить его в свой app.module, это пример того, каким должен быть контроллер (серверная часть):

@Controller()
export class Uploader {
  @Post('sampleName')
  @UseInterceptors(FileInterceptor('file'))
  uploadFile(@UploadedFile() file) {
  // file name selection 
    const path = `desired path`;
    const writeStream = fs.createWriteStream(path);  
    writeStream.write(file.buffer);
    writeStream.end();
    return {
      result: [res],
    };
  }
}

И вызвать ваш контроллер, принести в переднем конце:

    fetch('controller address', {
          method: 'POST',
          body: data,
        })
          .then((response) => response.json())
          .then((success) => {
            // What to do when succeed 
});
          })
          .catch((error) => console.log('Error in uploading file: ', error));

Вы можете использовать библиотеку apollo-upload-server. Похоже, что проще всего сделать, на мой взгляд. ура

Другие вопросы по тегам