-
Notifications
You must be signed in to change notification settings - Fork 424
[Proposal] File Uploads #65
Comments
That sounds pretty cool, I'd be interested to hear more about how people do this with |
Using Koa v2 on the server. I have a custom I really like what @jangerhofer is proposing. Maybe we can support file uploads without even touching the core. We can create custom As for the client. Can we somehow pull it of with |
Hmmm, OK, this is starting to sound pretty cool. What about this:
Alternative:
Other options? Still interested to see if this server component will be compatible with Relay - someone should go look at how that works. |
I did some analysis. Most of the Relay examples use
If that is what you mean? |
Two thoughts, @stubailo; first...
Perhaps I am alone in this, but I strongly prefer to avoid prescriptive solutions whenever possible. It's a totally reasonable proposition to have integrations with different services, but I would want to see that in an add-on package rather than within the base functionality. Second, I'm not sure I follow the requests that are made in steps 2+3. Are you thinking of using one request of type At any rate, if it is possible to determine which variables are of the file-type, then that's an even more elegant solution from the user's perspective! |
@jangerhofer You're not alone in this. That's why I prefer @stubailo's first suggestion:
This way the graphql layer remains transparent and flexible. I.e. this way @stubailo's second suggestion (S3 storage etc.) would be easy to implement / customize using already existing packages. |
If possible, I would prefer to send everything with a single request. I am trying to understand, what would need to be done, to support this on the apollo client/server, and also how we can support other graphql implementations. I'm looking at the Anyway, if we assume that the client makes a single I can start working on PR(s), but I would love to get some feedback from the core devs first. |
@HriBB I think that's a pretty reasonable approach. On the server side I would put the files on the context initially, and then try creating a special "file" input type. On the client, how exactly would the user specify a file upload so it gets appended properly in the network layer? |
I managed to get some work done on this. CLIENT Upload middleware will check if any variable is a
Use it like any other middleware
Mutation with files, simply pass
SERVER Upload middleware for
Use it before
Add
Also add resolver - a simple JSON will do for now ...
So the only thing that's blocking me ATM is the |
Some update I think it's actually better to use a custom network interface instead of middleware. UploadNetworkInterface.js
You can simply use it like this
And a slight improvement of server middleware, to support variable names
I will try to put this into a repo and also create Express example. Would love to get some feedback on this. |
@jangerhofer what do you think? |
If I was using s3 or some other file service I would not like to upload anything to my graphql server but rather get a token and have my users directly upload to s3/whatever. |
@zimme you don't need any of this stuff in your case. But you could write a middleware that uploads files to s3/whatever, replaces variables with tokens and pass them to the mutation. My proposal involves both apollo client and server. |
Yeah, I guess a regular query to get a valid upload token for s3 and use that to directly upload to s3 would be enough in that case. |
So, if I have a REST endpoint which handles form input with file upload, what would be the recommended approach with Graphql Server? Should I split the file upload endpoint from the form data endpoint in order to have this supported in Graphql Server? |
You can always re-use REST endpoint for file uploads. But then you cannot change client data with mutations when doing file uploads. |
And the Express upload middleware. Will put this into a repo when I find some spare time.
Use it like this.
Not really sure if applying multiple middleware is a good / the best approach, but it works! |
@HriBB Looks interesting! Do I understand correctly that I can do file uploads using a GraphQL mutation this way, by assigning the file to a mutation field? |
Yes. I have implemented this in two apps now, and it works well so far. |
@HriBB could you implement and publish a reusable higher-order function to decorate the network interface on the client and middleware for the server (express, for instance)? |
@thebigredgeek I have created two packages: apollo-upload-network-interface and graphql-server-express-upload middleware. Let me know if it works for you. Would love to get some feedback on this, especially from core devs. I only tested locally using |
@sandervanhooft can you open a new issue to have a discussion about what should and shouldn't be in the 1.0 milestone? That way it will be easier for other people to find. If there's enough demand for features that aren't currently part of it, we'll consider adding them. |
@helfer Thanks for the suggestion!. Done. |
@helfer @sandervanhooft @HriBB @stubailo You guys have a suggestion to make it easy/seamless using react-native? Since we just create an object for the file-upload the Thanks! |
Thanks for pointing that out.
What are the requirements/possibilities from the react-native perspective?
I only deal with (web) ReactJS, so I'm not familiar with React Native (i.e.
'check' logic).
…On Wed, 30 Nov 2016 at 21:30, Guilherme Decampo ***@***.***> wrote:
@helfer <https://github.com/helfer> @sandervanhooft
<https://github.com/sandervanhooft> @HriBB <https://github.com/HriBB>
@stubailo <https://github.com/stubailo>
You guys have a suggestion to make it easy/seamless using react-native?
Since we just create an object for the file-upload the check logic does
not fit because it relies on FileList / File instanceof as you can see
here
https://github.com/HriBB/apollo-upload-network-interface/blob/master/src/UploadNetworkInterface.js#L21
and here
https://github.com/HriBB/apollo-upload-network-interface/blob/master/src/UploadNetworkInterface.js#L47
.
Thanks!
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#65 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AG7dpxs2_SwjVrNrJmvP6j-8keGhZbI4ks5rDdzpgaJpZM4J5XgQ>
.
|
@felixk42 indeed, you have to use XHR until fetch supports progress. I just found this, great! I considered doing the same but then reasoned that it would require changing the networkinterface and it wouldn't work for all transports. Instead I implemented upload via REST, which returns an id that will be used by a graphql mutation to store the file in a relevant object. That is pretty straightforward, but having a standard way to do it in Apollo allows choosing how/where you upload, separately from your app logic. So if Apollo supports this as a separate concept from the NetworkInterface via a side-channel with the server, you could plug in form upload, S3 pre-signed encrypted upload etc, you could even send a stream via websocket. The mutation on the server would be executed only once the file is uploaded. Of course, uploading a file is non-trivial, so the mutation has to handle rollback in case the upload succeeds but the mutation fails. Furthermore, it could be useful to let a mutation weigh in on whether an upload is allowed. So the upload side-channel should also allow inspecting the mutation with variables before the upload starts. So, I'm seeing these required parts:
This is how I think it would work:
IMHO this would provide lots of benefit without much change to the existing graphql infrastructure; it would even work with a non-apollo graphql server. |
@wmertens Yep for now I am modifying the networkInterface to make it work. |
@wmertens: using the sideloader would mean you need to make 2 calls in total as I understand it. If you have for example an unstable mobile connection, rolling back would get really complex. I'd prefer a single atomic graphql call. |
On Sun, Feb 19, 2017 at 10:01 AM Sander van Hooft ***@***.***> wrote:
> @wmertens <https://github.com/wmertens>: using the sideloader would
mean you need to make 2 calls in total as I understand it. If you have for
example an unstable mobile connection, rolling back would get really
complex.
By rolling back, do you mean deleting the uploaded file on the server side?
Yes, I was thinking that'd be a job for garbage collection, if you didn't
get a mutation with that uploadId in 10 minutes, delete the upload.
> I'd prefer a single atomic graphql call.
While that would certainly be nice, how would that work? Would you specify
the file as a scalar and send it as part of the mutation variables?
Come to think of it, this would be possible… for graphql over HTTP you
could send those mutations as multipart form uploads, and the regular
variables would be JSON-encoded in e.g. `variables`.
Of course, this has some disadvantages:
- Needs support in the graphql server
- HTTP(s) only
- Cannot upload directly to some other location like S3
- No parallel uploads
But it does seem really handy for quick uploads like an avatar picture.
|
Hi @wmertens,
Have you looked into this solution?
https://github.com/HriBB/apollo-upload-network-interface
It works fine for me.
…On Sun, 19 Feb 2017 at 11:41, Wout Mertens ***@***.***> wrote:
On Sun, Feb 19, 2017 at 10:01 AM Sander van Hooft <
***@***.***>
wrote:
> > @wmertens <https://github.com/wmertens>: using the sideloader would
> mean you need to make 2 calls in total as I understand it. If you have
for
> example an unstable mobile connection, rolling back would get really
> complex.
>
By rolling back, do you mean deleting the uploaded file on the server side?
Yes, I was thinking that'd be a job for garbage collection, if you didn't
get a mutation with that uploadId in 10 minutes, delete the upload.
> > I'd prefer a single atomic graphql call.
>
While that would certainly be nice, how would that work? Would you specify
the file as a scalar and send it as part of the mutation variables?
Come to think of it, this would be possible… for graphql over HTTP you
could send those mutations as multipart form uploads, and the regular
variables would be JSON-encoded in e.g. `variables`.
Of course, this has some disadvantages:
- Needs support in the graphql server
- HTTP(s) only
- Cannot upload directly to some other location like S3
- No parallel uploads
But it does seem really handy for quick uploads like an avatar picture.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#65 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AG7dp6QC0abJ7id5r31irz2aaFhEm1izks5reBxhgaJpZM4J5XgQ>
.
|
Just published apollo-upload-server and apollo-upload-client. Hopefully this will make things a lot easier in the meantime. No need to setup multer or deal with custom scalars. It allows you to use File objects, FileList objects, or File arrays anywhere within mutation or query variables. You can name variables whatever, and have multiple sets of files in one mutation. You can decide if you want the files to be in a list or singular. The files upload to a configurable temp directory; the file paths and metadata will be available under the variable name in the resolver. |
@jaydenseric great! Could you expand on how your solution differs from https://github.com/HriBB/apollo-upload-network-interface ? |
@jaydenseric Oh, I see now that you already did that when you said "no adding multer, no custom scalars" :) |
@wmertens Probably the next most obvious difference is that you can upload single files without having to always use a list. You can also input FileList objects or arrays of files just the same. In theory you can also use more complicated input structures containing files, as files are looked for recursively. |
@jaydenseric Is your solution compatible with query batching?
…On Fri, 24 Feb 2017 at 15:42, Jayden Seric ***@***.***> wrote:
@wmertens <https://github.com/wmertens> Probably the next most obvious
difference is that you can upload single files without having to always
use a list
<HriBB/apollo-upload-network-interface#10>. You
can also input FileList objects or arrays of files
<HriBB/apollo-upload-network-interface#4> just
the same. In theory you can also use more complicated input structures
containing files, as files are looked for recursively.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#65 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AG7dp3yyjHasUrKNfOaWM_kBla5zHB7Rks5rfuxEgaJpZM4J5XgQ>
.
|
@sandervanhooft Not yet, I intend to add an additional |
I suppose easiest would be to batch regular requests but not uploads?
…On Tue, Feb 28, 2017, 12:13 PM Jayden Seric ***@***.***> wrote:
@sandervanhooft <https://github.com/sandervanhooft> Not yet, I intend to
add an additional createBatchingNetworkInterface export once I start
optimizing the app I'm working on. An earlier pull request would be
welcomed 🙂
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#65 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AADWlq9wUb5CXK0ApBDiU2MQ_z2-ZeALks5rhAFNgaJpZM4J5XgQ>
.
|
@sandervanhooft @wmertens apollo-upload-server and apollo-upload-client now support query batching 🎉 import ApolloClient from 'apollo-client'
import {createBatchNetworkInterface} from 'apollo-upload-client'
const client = new ApolloClient({
networkInterface: createBatchNetworkInterface({
uri: '/graphql',
batchInterval: 10
})
})
|
Sweet! Adding it to my project ASAP!
(prediction: when the graphql-over-websocket thing is finished, there will
be a need for a networkinterface that does uploads over HTTP and other
queries over websocket)
…On Sun, Mar 26, 2017, 4:26 PM Jayden Seric ***@***.***> wrote:
@sandervanhooft <https://github.com/sandervanhooft> @wmertens
<https://github.com/wmertens> apollo-upload-server
<https://github.com/jaydenseric/apollo-upload-server> and
apollo-upload-client <https://github.com/jaydenseric/apollo-upload-client>
now support query batching 🎉
import ApolloClient from 'apollo-client'import {createBatchNetworkInterface} from 'apollo-upload-client'
const client = new ApolloClient({
networkInterface: createBatchNetworkInterface({
uri: '/graphql',
batchInterval: 10
})
})
apollo-upload-server will automatically handle regular or batch requests.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#65 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AADWlpPNQESiW-CB0EhMPvWzUcqbv2Llks5rpnWUgaJpZM4J5XgQ>
.
|
@jaydenseric Your project is amazing, using for my new project, hope you will keep developing it! |
When will the Apollographql team support file uploads? |
@jaydenseric Awesome package! Just got it setup and works so smoothly :) |
@jaydenseric: I have to copied and modified your code to make it work with react-native. :p.
And edited in function
Also on each network-interface, i removed And every things work as expect. Happing coding. :p Ps: Copied from the old version of |
Hello everyone! It's cool that there is some community code floating around and even a package - what's in between this and including it as official thing? Almost all of the main Apollo Client features were built initially by the community. If you have a great idea for file uploads, let's make it happen! Anybody up for it? |
@stubailo |
@stubailo I would be keen to work with you. Basically, the network interface crawls operation variables to find files. If there are any, it prepares a multipart request. There is a convention for how the files get split out into their own multipart fields so that the server can identify that the variables need to be reassembled, and how. In theory, you could also add an alternative Base64 encoding strategy to support vanilla POST and GET requests. It's not something that excites me because it's less efficient and I don't use GET. Something that Apollo does not have (?), is an API for tracking loading progress. That would be really nice to tap into for heavier requests containing files. I guess that would mean abandoning It's been on my mind to investigate providing file streams in resolver arguments instead of file path strings. This would allow people to stream the file straight into MongoDB GridFS, etc. if they so choose or store it directly where they would like on the server. |
Wow, just looked more at those packages and they look pretty straightforward to use! It looks like the client side is more finalized than the server, where it's not exactly clear where the files should go. So maybe we can start there? |
@stubailo There seems to be a lot of change coming to transport in Apollo with the new fetcher, etc. Will we implement with the current, or future interfaces? jaydenseric/apollo-upload-client#21 (comment) |
It feels like the new |
This is something that wouldn't be part of |
After toying with the Express-GraphQL file upload demo, I have been thinking about how to bring file uploads inside GraphQL mutations to Apollo.
Here is the rundown of what I've learned and the best solution I have formulated:
Canonical file uploads happen via a
mutipart/form-data
POST request, whereas Apollo Client interfaces with the Server exclusively withapplication/json
requests at present.There are other ways to transmit a file from browser to server, but they are often flimsy and poor solutions.
The ideal solution delivers both a GraphQL mutation and the uploaded file(s) to the server in such a way that the schema resolvers have access to the files, such that custom logic can test, modify, and/or store the uploads. The best way to ensure both mutation string and file(s) reach the server at the same time is to merge them into the same request.
Thus, I think that Apollo Client needs to either
mutateWithFiles( "gqlQueryString", { apolloOptions }, [ files ] )
oruploadFiles : [ ... ]
which, when used, sends a
multipart/form-data
POST to the specified Apollo Server endpoint. The request would be identical to the normal request apart from the inclusion of a newfiles
field or similar which contains the uploading files. I think I would lean towards the clarity of a new method on account of readability.Apollo Server will then need to check each request for its
content-type
; if it isapplication/json
, then no upload-specific logic happens and the mutation continues as usual. If, however, the POST is ofcontent-type
multipart/form-data
, the server will append the files in memory to the root query object. The mutation then continues as usual.root
argument and subsequently to connectors if need-be.Please chime in if you have any thoughts. I'd especially like for members of the Apollo team to offer their opinions. If we get to a design that satisfies the dev team, I am happy to work alongside any other interested parties to create a PR!
The text was updated successfully, but these errors were encountered: