Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

File Upload Not Working #242

Closed
shadyendless opened this issue Dec 29, 2020 · 18 comments
Closed

File Upload Not Working #242

shadyendless opened this issue Dec 29, 2020 · 18 comments

Comments

@shadyendless
Copy link

Hello!

I recently switched from Apollo Client over to graphql-request because of my switch to using SWR for all of our requests. As part of this switch, file uploads appear to have broken across the site.

Following the documentation on the front of the page does not appear to make things work as intended.

Here is my query:

mutation($deckId: ID!, fileData: Upload!) {
  uploadFlashcards($deckId: ID!, fileData: Upload!) {
    errors {
      key
      message
    }
    status
  }
}

And here are the variables that are being sent along (as copy/pasted from my browser console):
image

The issue is that the request that is being sent to the server does not indicate that it has any files or anything of the like.
Headers

POST /api HTTP/1.1
Host: localhost:4000
Connection: keep-alive
Content-Length: 295
accept: application/json
authorization: Bearer <token>
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36 Edg/87.0.664.66
content-type: application/json
Origin: http://localhost:3000
Sec-Fetch-Site: same-site
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9,ja;q=0.8

Payload

{
  "query":"
    mutation($deckId: ID!, fileData: Upload!) {
      uploadFlashcards($deckId: ID!, fileData: Upload!) {
        errors {
          key
          message
        }
        status
      }
    }
  ",
  "variables": {
    "deckId":"d7dbd1bf-bcfe-409a-b4da-1b2b7e0f2d02",
    "fileData":{}
  }
}

Any assistance is greatly appreciated here.

@dxmxnlord
Copy link

same issue here, let me know if you solved it please !

@srttk
Copy link

srttk commented Jan 23, 2021

Update latest version fixed the issue

@guiquintelas
Copy link

For me the version 3.4.0 it's not working but 3.3.0 is

@gblikas
Copy link

gblikas commented Sep 29, 2021

@lynxtaa I saw that you had a PR #175 for Upload spec support. I am at the point where I'm trying to upload a file, via the Upload scaler, and with graphql-codegen, in the most-recent version of Apollo v3; this requires integration with graphql-upload.

As it pertains to this issue, and PR #175, do you have an example on how to use the GraphQLClient, and typescript-graphql-request to upload a file? This might be best solved with some solution to #242 ?...

I'm faced with the same (similar) issue as @shadyendless . The incoming request nullifies the file data:

const sdk = await getSdk(graphqlClient); 
const sdkUploadFileVariables = {
      file: new Promise(resolve => resolve({
        filename: 'twitter.png',
        mimetype: 'image/png',
        encoding: '7bit',
        createReadStream(): ReadStream {
          return fs.createReadStream("./test_files/twitter.png");
        },
      }))}
await sdk.uploadFile(sdkUploadFileVariables); 

// `file: { }`

Similarly, if I follow https://github.com/prisma-labs/graphql-request#file-upload, I get an unexpected token error,

SyntaxError: Unexpected token o in JSON at position 1
    at JSON.parse (<anonymous>)

For me the version 3.4.0 it's not working but 3.3.0 is

@guiquintelas This downgrade did not work for me, neither did an upgrade to v3.5.0.

@lynxtaa
Copy link
Contributor

lynxtaa commented Sep 30, 2021

@gblikas In graphql-request there is a heuristic for detecting files in variables https://github.com/prisma-labs/graphql-request/blob/a6d1365ae0fc6694c45ea404e4a44b93a8c06479/src/createRequestBody.ts#L10 it supports instances of File and Blob and duck-types NodeJS streams.

Does it work without typescript-graphql-request?

Sorry, I don't have any examples because right now I'm using my own implementation of graphql client and not using graphql-request

@gblikas
Copy link

gblikas commented Oct 1, 2021

Does it work without typescript-graphql-request?

@lynxtaa I poked around with awesome-graphql-client, but I'd like to stick with graphql-request, since it is an SDK generator via graphql-codegen...

https://github.com/prisma-labs/graphql-request/blob/a6d1365ae0fc6694c45ea404e4a44b93a8c06479/src/createRequestBody.ts#L10

works without typescript-graphql-request, and with it; the end of

https://github.com/prisma-labs/graphql-request/blob/a6d1365ae0fc6694c45ea404e4a44b93a8c06479/src/createRequestBody.ts#L19

has a FormData object, with valid FormData.entries() - I am unsure if it is properly formatted, but when cross-referenced with Postman, both FormDatas have the same content:

[
      'operations',
      '{ "query":"mutation uploadFile($file: Upload!){uploadFile(file: $file){ id }}"}'
]
[ 'map', '{"1":["variables.file"]}' ]
[ '1', '[object Object]' ]

ℹ️ FYI, Postman does send through a valid File, and Upload, via multipart/form-data.


@lynxtaa After taking your comments into consideration, and using multipart/form-data header, from what I can tell, the error seems to lay with Busboy, and graphql-upload, regardless of using GraphQLClient, or request?;

BadRequestError: Missing multipart field ‘operations’ (https://github.com/jaydenseric/graphql-multipart-request-spec).
    at Busboy.<anonymous> (/Users/username/projects/projectName/node_modules/graphql-upload/public/processRequest.js:330:11)
    at Object.onceWrapper (events.js:421:28)
    at Busboy.emit (events.js:327:22)
    at Busboy.EventEmitter.emit (domain.js:467:12)
    at Busboy.emit (/Users/username/projects/projectName/node_modules/busboy/lib/main.js:37:33)
    at /Users/username/projects/projectName/node_modules/busboy/lib/types/multipart.js:304:17
    at processTicksAndRejections (internal/process/task_queues.js:75:11)

Perhaps, @jaydenseric can shed some light on this issue? Here are the relevant packages I'm using,

// package.json

"apollo-server": "2.22.2",
"apollo-server-express": "^3.3.0",
"graphql-upload": "^12.0.0",
"graphql-request": "^3.5.0",

Although I installed apollo-server, it is not inuse, we are using apollo-server-express only. We also have, {upload: false} in our ApolloServer config, however this shouldn't matter, since v3.3.0 has removed scalar Upload support.


I also found some other github issues that seem to be related to this one (or legacy solutions, which have not proved helpful); jaydenseric/graphql-upload#241, jaydenseric/graphql-upload#238

@jaydenseric
Copy link

@gblikas use the Chrome inspector network tab to double check the client is sending a valid GraphQL multipart request.

ℹ️ FYI, Postman does send through a valid File, and Upload, via multipart/form-data.

Do you mean to say the files upload and are processed by the GraphQL API correctly when you do the file upload requests via Postman? In that case you problem is not the server; the client is not sending valid requests.

Although I installed apollo-server, it is not inuse, we are using apollo-server-express only.

Uninstall it. Why bloat your node_modules with megabytes of junk and confuse us with that detail? Uninstalling it also makes sure it's not accidentally being used somewhere in your codebase.

We also have, {upload: false} in our ApolloServer config, however this shouldn't matter, since v3.3.0 has removed scalar Upload support.

Remove that config that does nothing.

@gblikas
Copy link

gblikas commented Oct 1, 2021

ℹ️ FYI, Postman does send through a valid File, and Upload, via multipart/form-data.

Do you mean to say the files upload and are processed by the GraphQL API correctly when you do the file upload requests via Postman? In that case you problem is not the server; the client is not sending valid requests.

@jaydenseric Yes, this seems to be the case; this is why the issue is in graphql-request. I mention you in this issue, because jaydenseric/graphql-upload#238 is very similar but doesn't work, as per Busboy. 🤔 Wondering about input from @lynxtaa , now.

@lynxtaa
Copy link
Contributor

lynxtaa commented Oct 1, 2021

@gblikas Could you create a repository with a minimal reproduction of this issue? I'll look into it

P.S. awesome-graphql-client can be used with generated types via TypedDocumentNode. But all the logic for file upload is the same as in graphql-request so I don't think it'll help

@lynxtaa
Copy link
Contributor

lynxtaa commented Oct 1, 2021

Also I noticed that graphql-request uses form-data package for NodeJS which is highly popular but not spec-compliant and its usage is discouraged node-fetch/node-fetch#1212

@gblikas
Copy link

gblikas commented Oct 6, 2021

@gblikas Could you create a repository with a minimal reproduction of this issue? I'll look into it

@lynxtaa Will do!

@gblikas
Copy link

gblikas commented Oct 29, 2021

@gblikas Could you create a repository with a minimal reproduction of this issue? I'll look into it

@lynxtaa Will do!

@lynxtaa sorry that it took so long! Here is a very bare-bones implementation of the error in question; it was based on the graphql-upload example code,

In order to run the sample, make sure the server is live, and then open another terminal;

# graphql-codegen to build the Request SDK, [as per the examples](https://www.graphql-code-generator.com/) 
npm run generate 
# run a node-based GraphQL request using the Requests SDK for file uploading, [as per the graphql-upload docs](https://github.com/jaydenseric/graphql-upload#function-graphqluploadexpress) 
npm run test

⚠️ I left the Content-Type header "application/json". If you want to see the upload error, please change the Content-Type to multipart/form-data, etc..

Let me know if you're able to reproduce this error.

@lynxtaa
Copy link
Contributor

lynxtaa commented Oct 30, 2021

@gblikas

  1. You shouldn't set Content-Type header when sending multipart/form-data via fetch. Fetch by spec must set Content-Type to multipart/form-data; boundary=, followed by the multipart/form-data boundary string generated by the multipart/form-data encoding algorithm: https://fetch.spec.whatwg.org/#bodyinit-unions
  2. Just putting a stream as a graphql variable will work

Check out https://codesandbox.io/s/unruffled-paper-rxxct?file=/test.ts

@gblikas
Copy link

gblikas commented Oct 30, 2021

@gblikas

  1. You shouldn't set Content-Type header when sending multipart/form-data via fetch. Fetch by spec must set Content-Type to multipart/form-data; boundary=, followed by the multipart/form-data boundary string generated by the multipart/form-data encoding algorithm: https://fetch.spec.whatwg.org/#bodyinit-unions
  2. Just putting a stream as a graphql variable will work

Check out https://codesandbox.io/s/unruffled-paper-rxxct?file=/test.ts

@lynxtaa The sandbox worked great. I'll take a moment to integrate this in to our internal framework, and get back with a confirmation of it working.


@lynxtaa For next time, where could I have found better documentation around how to upload a file using graphql-request? Uploading a file progamatically, via graphql-<package> isn't mentioned anywhere, out of perhaps knowing what you pointed out in (1) and (2), already. Perhaps I wasn't looking in the right area?

@lynxtaa
Copy link
Contributor

lynxtaa commented Oct 31, 2021

@gblikas I'm glad it helped!

@lynxtaa For next time, where could I have found better documentation around how to upload a file using graphql-request? Uploading a file progamatically, via graphql-<package> isn't mentioned anywhere, out of perhaps knowing what you pointed out in (1) and (2), already. Perhaps I wasn't looking in the right area?

You can check out sources, they are rather minimal https://github.com/prisma-labs/graphql-request/tree/master/src

Under the hood graphql-request uses extract-files to detect streams, Blobs and Files in variables and change request body to a multipart/form-data according to a GraphQL File Upload spec

@gblikas
Copy link

gblikas commented Nov 1, 2021

@shadyendless Did the solution @lynxtaa and I have been discussing help solve your current problem?

@luongvm
Copy link

luongvm commented Mar 10, 2022

Hi there, maybe it's obvious to others but since I spent sometime figuring out why my generated sdk client cannot upload file I will describe what happened to me here:

  1. I used codegen to generate a sdk.ts file.
  2. Use @golevelup/nestjs-graphql-request inside my nestjs application.
  3. Use the sdk inside some controller to upload file to a graphql server and fail miserably.
  4. Find the issue here and try everything here.
  5. Solved by deleting the Content-Type header inserted when I declare the module (I just follow the instruction in the 2nd step package README):
GraphQLRequestModule.forRootAsync(GraphQLRequestModule, {
      imports: [],
      inject: [ConfigService],
      useFactory: async (configService: ConfigService) => ({
        // Exposes configuration options based on the graphql-request package
        endpoint: configService.get('API_URL'),
        options: {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json', <=== delete this
            Authorization: 'Bearer ' + configService.get('API_TOKEN'),
          },
        },
      }),
    }),

It seems the Content-Type header is not overridden automatically by whatever handled the actual request.

@jasonkuhrt
Copy link
Member

#500

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants