Graphql-js: File Upload Example?

Created on 20 Jul 2017  路  11Comments  路  Source: graphql/graphql-js

I am trying to create a custom input type using vanilla graphql (ie: non-apollo).

ref: https://github.com/jaydenseric/apollo-upload-server/issues/9

Got any examples for uploading files?

Most helpful comment

memo for who wanna use other client with import { GraphQLUpload } from 'apollo-upload-server'

nodejs

import fs from 'fs'
import FormData from 'form-data'
import axios from 'axios'

let o = {
  query: `mutation ($file: Upload!) {
    uploadFile (file: $file)
  }`,
  variables: {
    file: null
  }
}
let map = {
  '0': ['variables.file']
}
let fd = new FormData()
fd.append('operations', JSON.stringify(o))
fd.append('map', JSON.stringify(map))
fd.append(0, fs.createReadStream(`${__dirname}/app.js`), 'app.js')

let res = await axios.post('/graphql', fd, {
  headers: {
    ...fd.getHeaders()
  }
})
expect(res.status).to.be.equal(200)
expect(res.data).to.be.jsonSchema(schema)

All 11 comments

@appjitsu, for an app I'm working on where I need to be able to receive multiple uploaded files as part of a mutation, I came up with the idea of accepting a multipart/form-data request where the first part is application/graphql and the subsequent ones are arbitrary files that are associated with the mutation.

The multipart/form-data handling is a piece of middleware, so it ought to be reusable. I've uploaded it here along with a very simple usage example: https://gist.github.com/papandreou/abd7b8f00f7bff699a06e21330a2004a

I have no idea if there's a better, more "official" way of achieving the same thing, though :)

Perhaps not exactly what you are after since it does use Apollo, but you might find jaydenseric/apollo-upload-examples helpfull.

@appjitsu, please take a look at some of the suggestions for handling file upload. Closing this issue for, feel free to re-open if you believe it's unresolved.

memo for who wanna use other client with import { GraphQLUpload } from 'apollo-upload-server'

nodejs

import fs from 'fs'
import FormData from 'form-data'
import axios from 'axios'

let o = {
  query: `mutation ($file: Upload!) {
    uploadFile (file: $file)
  }`,
  variables: {
    file: null
  }
}
let map = {
  '0': ['variables.file']
}
let fd = new FormData()
fd.append('operations', JSON.stringify(o))
fd.append('map', JSON.stringify(map))
fd.append(0, fs.createReadStream(`${__dirname}/app.js`), 'app.js')

let res = await axios.post('/graphql', fd, {
  headers: {
    ...fd.getHeaders()
  }
})
expect(res.status).to.be.equal(200)
expect(res.data).to.be.jsonSchema(schema)

Thanks so much @up9cloud. This is a really weird api if you ask me. Thanks to you I was able to make a function that correctly makes the file map from a graphql variables object. Here it is in case this helps someone: https://github.com/zackify/react-uploader/blob/master/src/clients/graphql/create-file-map.js

I no longer use Apollo on the server or client these days, and use just the vanilla GraphQL.js API.

There is a simple server side example of both approaches here:

https://github.com/jaydenseric/graphql-upload#class-graphqlupload

And the best reference for a client library that constructs a fetch request handling possible files is here:

https://github.com/jaydenseric/graphql-react/blob/1b1234de5de46b7a0029903a1446dcc061f37d09/src/universal/graphqlFetchOptions.mjs

An example of uploading a file from a server environment:

https://github.com/jaydenseric/graphql-upload/blob/9b49be4cc5012307e7a08deb6d08ec071a708d5d/src/test.mjs#L79

Alternatively, the GraphQL multipart request spec has example cURL requests:

https://github.com/jaydenseric/graphql-multipart-request-spec#curl-request

This is a really weird api if you ask me.

Rest assured, the spec has to be the way it is for good performance and to handle all sorts of situations like a client disconnecting half way through uploading several files, or using the same file as a variable in multiple mutations.

Only library authors really need to understand how it works; users of server and client implementations can take it for granted.

@jaydenseric thank you for the reply! The spec being that way now makes sense to me. Your client example helps a lot as well.

@jaydenseric have to thank you again! Spent hours online to find a client solution and not using Apollo. The client demo is very helpful. Thanks!

For future readers, note the header is using { Accept: 'application/json' } when you upload file. I was using 'Content-Type': 'application/json' which gave me error and I didn't notice in the beginning.

The operation input in the graphqlFetchOptions(operation) is something like

{
  query: `
    mutation UploadFile($file: Upload!) {
      uploadFile(file: $file) {
        id
      }
    }
  `,
  variables: {
    file: event.target.files,
  },
}

The backend you can use @jaydenseric's package graphql-upload if you do not want to use any framework like Apollo.

@up9cloud after 3 days looking for a solution, your solution worked for me, thanks my friend, you're the real MVP

@up9cloud thanks! Your approach was very useful to me as well 馃憤

@up9cloud Thank you very much !
For one who has error ...createReadStream is not a function...
I changed

fd.append(0, fs.createReadStream(${__dirname}/app.js), 'app.js')

to
fd.append(0, file)

It will work but I don't understand why or what is the different.
I appreciate some explanations. Thanks again !

Was this page helpful?
0 / 5 - 0 ratings