Как загрузить изображения в бэкэнд fastify + graphql с помощью axios?

При отправке изображений через axios я обнаружил, что должен использовать formdata. Я добавляю сюда свои изображения, но при отправке данных формы весь мой бэкэнд просто зависает, просто говорит «ожидает».

Я следил за этим

И моя попытка до сих пор:

бэкэнд:

Аполлон:

import { ApolloServer, makeExecutableSchema } from 'apollo-server-fastify';

const schema = makeExecutableSchema({ typeDefs, resolvers });

const apolloServer = new ApolloServer({
  schema,
  uploads: {
    maxFileSize: 10000000,
    maxFiles: 5,
  },
});

(async function() {
  app.register(apolloServer.createHandler({ path: '/api' }));
})();

схема:

  scalar DateTime
  scalar Upload

  input addUser {
    Email: String!
    Password: String
    FirstName: String!
    LastName: String!
    Age: DateTime!
    JobTitle: String!
    File: Upload
  }

  type Mutation {
    register(input: addUser!): Boolean
  }

резолвер:

  Mutation: {
    register: async (obj, args, context, info) => {
        // how to get the formData?
      },
  }

Внешний интерфейс:

Я строю запрос так:

const getMutation = (mutate: MutationNames, returParams?: any): any => {
  const mutation = {
    login: print(
      gql`
        mutation($email: String!, $password: String!) {
          login(email: $email, password: $password) {
            token
            refreshToken
          }
        }
      `
    ),
    register: print(
      gql`
        mutation(
          $firstName: String!
          $email: String!
          $lastName: String!
          $age: DateTime!
          $jobTitle: String!
          $file: Upload
        ) {
          register(
            input: {
              FirstName: $firstName
              LastName: $lastName
              Email: $email
              Age: $age
              JobTitle: $jobTitle
              File: $file
            }
          )
        }
      `
    ),

  }[mutate];

  if (!mutation) return {};

  return mutation;
};

В этом случае я использую регистровую мутацию.

У меня есть несколько приемов о том, как я обрабатываю выборку данных, поэтому я не собираюсь включать их, поскольку это много кода. Данные извлекаются правильно во внешнем интерфейсе, и перед отправкой на бэкэнд я помещаю все в объект formData:

  const submitForm: SubmitForm = (obj: SendObject) => {
    const Fdata = new FormData();

    Fdata.append('0', fileImp.file);

    Fdata.append('operations', JSON.stringify(obj.data));

    const map = {
      '0': ['variables.file'],
    };
    Fdata.append('map', JSON.stringify(map));

    callAxiosFn(
      {
        method,
        url: 'http://localhost:4000/api',
        data: Fdata,
        // headers: obj.headers,
      },
      qlType.toString()
    );
  };

вызывается так:

  const response = await axios({
    headers: {
      Accept: 'application/json',
      'x-token': localStorage.getItem('token'),
      'x-refresh-token': localStorage.getItem('refreshToken'),
      ...(config.headers || {}),
    },
    ...config,
  });

конфигурация AxiosRequestConfig

Что я отправляю:

введите здесь описание изображения

Я точно не понимаю, как данные формы попадут в мою конечную точку преобразователя, и по этой причине я делаю что-то не так, так как бэкэнд возвращается:

(узел: 748) UnhandledPromiseRejectionWarning: [массив объектов] (узел: 748) UnhandledPromiseRejectionWarning: необработанное отклонение обещания. Эта ошибка возникла либо из-за вызова асинхронной функции без блока catch, либо из-за отклонения обещания, которое не было обработано с помощью .catch (). (идентификатор отклонения: 1) (узел: 748) [DEP0018] DeprecationWarning: необработанные отклонения обещаний устарели. В будущем необработанные отклонения обещаний завершат процесс Node.js с ненулевым кодом выхода.

Я понимаю, что это много, но я в конце моего визита здесь, был в этом весь день. Любая помощь очень ценится.

РЕДАКТИРОВАТЬ:

Поскольку мой бэкэнд был подвергнут сомнению, я подумал, что просто покажу, что при отправке данных без добавления Formdata, как я делаю выше, я получаю его работающим:

  const submitForm: SubmitForm = (obj: SendObject) => {

    callAxiosFn(
      {
        method,
        url: 'http://localhost:4000/api',
        data: obj.data,
      },
      qlType.toString()
    );
  };

obj.data:

{query: "mutation ($firstName: String!, $email: String!, $l… Age: $age, JobTitle: $jobTitle, File: $file})↵}↵", variables: {…}}
query: "mutation ($firstName: String!, $email: String!, $lastName: String!, $age: DateTime!, $jobTitle: String!, $file: Upload) {↵  register(input: {FirstName: $firstName, LastName: $lastName, Email: $email, Age: $age, JobTitle: $jobTitle, File: $file})↵}↵"
variables:
age: "1977-04-04"
email: "[email protected]"
file: File {name: "something.jpg", lastModified: 1589557760497, lastModifiedDate: Fri May 15 2020 17:49:20 GMT+0200 (centraleuropeisk sommartid), webkitRelativePath: "", size: 32355, …}
firstName: "Jhon"
jobTitle: "SomethingCool"
lastName: "Doe"
password: "CoolPassword!"123"
__proto__: Object
__proto__: Object

запрос отправляется в браузере:

введите здесь описание изображения

Серверная часть получает данные, но изображение не включено:  введите описание изображения здесь

РЕДАКТИРОВАТЬ:

Недавно обнаружил, что у моего бэкэнда fastify могут быть проблемы с чтением formData. попытался установить

fastify-multipart

но при регистрации возникли ошибки:

FST_ERR_CTP_ALREADY_PRESENT (contentType) ^ FastifyError [FST_ERR_CTP_ALREADY_PRESENT]:

После этого я попробовал:

npm uninstall fastify-file-upload

Ошибка осталась.


person CodingLittle    schedule 15.05.2020    source источник
comment
плохой синтаксис мутации - нет возврата содержимого def - вы должны что-то вернуть - по крайней мере, некоторые { id }   -  person xadm    schedule 15.05.2020
comment
file не null-ed   -  person xadm    schedule 15.05.2020
comment
Fdata.append('file'... - почему не 0? .... ИМХО, вы должны разделить проблемы ... получить рабочий рецепт клиента (1-я ссылка) и заставить работать бэкэнд ... затем воссоздать его с необходимыми настройками ... слишком много изменений одновременно   -  person xadm    schedule 15.05.2020
comment
Обновлен вопрос, чтобы показать полностью работающий бэкэнд, когда я не отправляю данные как formData. Проблема в том, что я не попадаю в резолвер, а также файл не включается.   -  person CodingLittle    schedule 16.05.2020
comment
mutation name( input ) { result } - в вашем запросе нет части результата (меня не волнует код) ..... начните с примера workimg formData (FE) и докажите, что BE работает ... затем адаптируйте FE под свои нужды   -  person xadm    schedule 16.05.2020
comment
Результат здесь пока менее важен. Я верну bolean, если все пойдет правильно, НО я вообще не попадаю в резолвер. Просто получаю UnhandledPromiseRejectionWarning. Начинаю думать, что мой fastify-server не умеет обрабатывать formData   -  person CodingLittle    schedule 16.05.2020
comment
ХМММ, не думаете ли вы, вам следует упомянуть об этом раньше? formData, вероятно, поддерживается, multipart, вероятно, нет (github.com/fastify/fastify/issues/1596), загрузка в graphql также требует дополнительной настройки - сервер должен знать, что делать с operations   -  person xadm    schedule 16.05.2020
comment
Если бы я знал, что это проблема, я бы упомянул об этом. Я прочитал эту проблему и получил FST_ERR_CTP_ALREADY_PRESENT (contentType) ^ FastifyError [FST_ERR_CTP_ALREADY_PRESENT]. попробовал unisnatlling npm uninstall fastify-file-upload, но ошибка осталась.   -  person CodingLittle    schedule 16.05.2020
comment
Пробовал эту библиотеку и получил то же самое: FST_ERR_CTP_ALREADY_PRESENT (contentType) ^ FastifyError [FST_ERR_CTP_ALREADY_PRESENT] Парсер содержимого есть. что-то еще не так   -  person CodingLittle    schedule 18.05.2020


Ответы (3)


Что ж, я еще не исследовал эту тему. Но я знаю, что axios с GraphQL на самом деле не так хорошо работает. Axios предназначен в основном для вызовов REST API. Однако мне очень нравится этот канал, Бен Авад, и я многому у него научился. Парень действительно классный, он все ясно и красиво объясняет. Но самое главное, он является энтузиастом GraphQL и исследует и представляет различные темы на эту тему, а также с React.js, TypeORM и PostgreSQL. Вот несколько полезных ссылок с его канала, которые могут помочь в решении вашей проблемы:

Надеюсь, это поможет! Пожалуйста, дайте мне знать, если содержание будет полезным!

person georgekrax    schedule 15.05.2020
comment
Очень полезные ссылки, и я также слежу за видео Бена! Я реализовал все с помощью axios, и теперь он отлично работает, за исключением попытки отправить данные файла. - person CodingLittle; 16.05.2020
comment
Я очень рад это слышать! Всегда приятно помогать людям. Пожалуйста, я был бы признателен, если бы Вы тоже приняли мой ответ. - person georgekrax; 16.05.2020
comment
Я ценю ссылки и считаю их полезными, но это не ответ на мой вопрос. Я отказываюсь признать, что это невозможно, я где-то лажаю. - person CodingLittle; 16.05.2020
comment
Хорошо, я тоже ценю твой честный ответ. По крайней мере, я надеюсь, что немного помог с вашей проблемой. - person georgekrax; 16.05.2020

Это заняло некоторое время, и обычно, когда вы принимаете что-то как должное, требуется время, чтобы найти ошибку.

Для тех, у кого есть такая же проблема, помните, что порядок, который вы добавляете, имеет значение!

Что я сделал:

const Fdata = new FormData();

Fdata.append('0', fileImp.file);  // NOTICE THIS

Fdata.append('operations', JSON.stringify(obj.data));

const map = { // NOTICE THIS
  '0': ['variables.file'],
};
Fdata.append('map', JSON.stringify(map));

Проблема: помните, когда я сказал, что порядок добавления вещей имеет значение? Дело в том, что отображение было добавлено после добавления файла.

Правильный способ:

const Fdata = new FormData();

Fdata.append('operations', JSON.stringify(obj.data));

const map = { // NOTICE THIS
  '0': ['variables.file'],
};
Fdata.append('map', JSON.stringify(map));
Fdata.append('0', fileImp.file);  // NOTICE THIS

Также обратите внимание, что в моем вопросе я пропустил установку самого файла на null в переменных:

  variables: {
    file: null,
  },

Это нужно сделать.

Дополнительную информацию см. здесь

person CodingLittle    schedule 19.05.2020
comment
Поздравляю, вы могли бы это сработать несколькими днями ранее ... просто более строго следуя руководству, которому вы следовали: D - person xadm; 20.05.2020

@CodingLittle рад, что вы выяснили, ответ был связан с упорядочением полей в составной форме.

Некоторые вещи, которые нужно добавить (отвечая, поскольку у меня нет 50 репутации, необходимой для того, чтобы прокомментировать ваш ответ, несмотря на то, что это graphql-upload автор)…

Также обратите внимание, что в моем вопросе я пропустил установку самого файла на null в переменных

Это правда, и хорошо разбираться в ней, хотя на самом деле много спецификации многостраничных запросов GraphQL серверные реализации просто заменят все, что находится в сопоставленном пути к файлу, скалярным значением загрузки, не заботясь о том, что там было - теоретически вы можете заменить файлы в переменных на asdf вместо null, и это все равно будет работать. JSON.stringify заменил бы экземпляры файлов чем-то вроде {}.

Многие из ваших головных болей можно было бы избежать, если бы серверная часть ответила четким статусом 400 и описательным сообщением об ошибке вместо того, чтобы выдавать грубую ошибку UnhandledPromiseRejectionWarning. Если ваша зависимость graphql-upload была актуальной на серверной части, вы должны были увидеть описательное сообщение об ошибке, когда запросы не соответствовали спецификации составных запросов GraphQL в отношении порядок полей, как показано в _ 8_ тесты:

https://github.com/jaydenseric/graphql-upload/blob/v11.0.0/test/public/processRequest.test.js#L929.

Попробуйте запустить npm ls graphql-upload в своем внутреннем проекте, чтобы убедиться, что установлена ​​только одна версия и последняя опубликована в npm (v11 на момент этого ответа). Обратите внимание: если вы полагаетесь на Apollo Server, чтобы установить его для вас, они используют очень устаревшую версию (v8).

person Jayden Seric    schedule 29.05.2020