graphql-request icon indicating copy to clipboard operation
graphql-request copied to clipboard

File Upload Not Working

Open shadyendless opened this issue 4 years ago • 17 comments

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.

shadyendless avatar Dec 29 '20 04:12 shadyendless

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

dxmxnlord avatar Jan 01 '21 02:01 dxmxnlord

Update latest version fixed the issue

saratonite avatar Jan 23 '21 15:01 saratonite

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

guiquintelas avatar Feb 04 '21 01:02 guiquintelas

@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.

gblikas avatar Sep 29 '21 22:09 gblikas

@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

lynxtaa avatar Sep 30 '21 07:09 lynxtaa

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); https://github.com/jaydenseric/graphql-upload/issues/241, https://github.com/jaydenseric/graphql-upload/issues/238

gblikas avatar Oct 01 '21 00:10 gblikas

@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.

jaydenseric avatar Oct 01 '21 01:10 jaydenseric

ℹ️ 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 https://github.com/jaydenseric/graphql-upload/issues/238 is very similar but doesn't work, as per Busboy. 🤔 Wondering about input from @lynxtaa , now.

gblikas avatar Oct 01 '21 03:10 gblikas

@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 avatar Oct 01 '21 16:10 lynxtaa

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 https://github.com/node-fetch/node-fetch/pull/1212

lynxtaa avatar Oct 01 '21 16:10 lynxtaa

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

@lynxtaa Will do!

gblikas avatar Oct 06 '21 23:10 gblikas

@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,

  • 🔗 https://codesandbox.io/s/zen-joana-6fy4z

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.

gblikas avatar Oct 29 '21 21:10 gblikas

@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 avatar Oct 30 '21 11:10 lynxtaa

@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?

gblikas avatar Oct 30 '21 20:10 gblikas

@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

lynxtaa avatar Oct 31 '21 06:10 lynxtaa

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

gblikas avatar Nov 01 '21 16:11 gblikas

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.

luongvm avatar Mar 10 '22 09:03 luongvm

https://github.com/jasonkuhrt/graphql-request/issues/500

jasonkuhrt avatar Apr 05 '23 12:04 jasonkuhrt