How to upload images to fastify + graphql backend with axios?

做~自己de王妃 提交于 2021-01-28 13:35:50

问题


When sending images via axios I found I have to use formdata. I add my images here but when sending the formdata my entire backend just freezes, just says "pending".

Ive been following this

And my attempt so far:

backend:

Apollo:

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' }));
})();

schema:

  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
  }

resolver:

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

FrontEnd:

I build the request like this:

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;
};

In this case im using the register mutation.

I have a few hooks on how I handle the data fetching so Im not going to include it since it is alot of code. The data is fetched correctly in the front end and before posting to the backend im putting everything to a formData object:

  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()
    );
  };

gets called like this:

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

config is AxiosRequestConfig

What Im sending:

I dont exactly understand How the formdata will hit my resolver endpoint and for that reason im doing something wrong since the backend returns:

(node:748) UnhandledPromiseRejectionWarning: [object Array] (node:748) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1) (node:748) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

I realize this is alot but Im at the end of my vits here, been at this the entire day. Any help is deeply appreciated.

EDIT:

Since my backend was questioned I thought I would just show that when sending data without appending Formdata like I do above then I get it working:

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

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

obj.data is:

{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: "JhoneDoe@hotmail.com"
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

query getting sent in the browser:

Backend reciving the data but the image is not included:

EDIT:

Recently found that my fastify backend might have issues with reading formData. tried installing

fastify-multipart

but got errors when registering it:

FST_ERR_CTP_ALREADY_PRESENT(contentType) ^ FastifyError [FST_ERR_CTP_ALREADY_PRESENT]:

After that I tried:

npm uninstall fastify-file-upload

Error remained.


回答1:


Well, I have not explored this topic yet. But I know that axios with GraphQL does not really work that well. Axios is made mainly for REST API calls. However, I really like and have learned a lot from this channel Ben Awad. The guy is really awesome and explains things clearly and nice. But the most important he is a GraphQL enthusiast and explores and presents various topic about it, as well with React.js, TypeORM & PostgreSQL. Here are some helpful links, from his channel, that might help with your issue:

  • Upload Files in GraphQL Using Apollo Upload
  • How to Upload a File to Apollo Server in React

I hope this helps! Please let me know if the content is helpful!




回答2:


This took some time and usally when you take something for granted it takes time to find the mistake.

For anyone having the same problem please remember that the order you add something MATTERS!

What I did:

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));

Problem: Remember when I said order of appending things matter? Well the case here was that the mapping was added after the file was added.

The correct way:

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

Also note that in my qestion I missed setting the file itself to null in the variables:

  variables: {
    file: null,
  },

This has to be done.

For more info read here




回答3:


@CodingLittle glad you figured out the answer was related to the multipart form field ordering.

Some things to add (answering as I don't have the 50 reputation required to make a comment on your answer, despite being the graphql-upload author)…

Also note that in my qestion I missed setting the file itself to null in the variables

This is true, and good to get right, although in reality a lot of GraphQL multipart request spec server implementations will simply replace whatever is at the mapped path for a file with the upload scalar value without caring what was there — in theory, you could replace files in variables with asdf instead of null and it would still work. JSON.stringify would have replaced the file instances with something like {}.

A lot of your headaches could have been avoided if the backend responded with a clear 400 status and descriptive error message instead of throwing a gnarly UnhandledPromiseRejectionWarning error. If your graphql-upload dependency was up to date on the backend, you should have seen a descriptive error message when the requests were not conforming to the GraphQL multipart request spec regarding field ordering, as can be seen in the graphql-upload tests:

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

Try running npm ls graphql-upload in your backend project to check only one version is installed, and that it’s the latest published to npm (v11 at the time of this answer). Note that if you’re relying on Apollo Server to install it for you, they use a very out of date version (v8).



来源:https://stackoverflow.com/questions/61827041/how-to-upload-images-to-fastify-graphql-backend-with-axios

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!