Search code examples
javagraphqlmultipartform-datagraphql-javarelaymodern

File uploading with Relay Modern & GraphQL


So I've been on this for two days and am not savvy enough in Java to fully realize an approach to solving this issue. On the client-side I've found some excellent user documentation and believe I have the proper pieces in order but when it comes to handling inbound multipart/formdata in the server, I am a bit lost.

The foundation for my application is based upon the HowToGraphQL Java tutorial as I feel they did a great job organizing their code base and make things very simple and understandable. Unfortunately file uploading is not an area they cover, and per a maintainer of the graphql-java package, no plans exist to directly support this feature.

However this is not to say this is not possible. The GraphQL specification was meant to be a guideline and how the community chooses to invoke the specs is up to them. A number of people have been successful in incorporating file uploading, albeit often through the inclusion of custom middleware

The roadblock for me is likely just lack of domain knowledge unfortunately. I work mostly with JavaScript and Python and mostly have academic experience with Java. Not entirely sure where to begin but I imagine I will need to track down the response object which I think is tied into the SimpleGraphQLServlet class and find a way to extract inbound file objects.

Would appreciate some more expert advice or help in coming up with a solution. I haven't found any solid Java based snippets on SO to address this so solving this would likely benefit others attempting to adopt this platform.

Edit:

Possibly found a means to do this, but still haven't worked out the specifics. In the GraphQLEndpoint class which extends the SimpleGraphQLServlet class we expose the doPost() method. Extracting the data however ends up resulting in a series of errors, the first being resolved with a @MultipartConfig annotation, the next error however leaves me once again stalled. "Missing content for MultiPart request."

This doPost() method seems to be called after the mutation occurs, so even if I manage to get the file data extracted, it seems that the data will be in the wrong place. Additionally I wonder if the missing content is the result of the Mutation consuming the post data, assuming it even arrived at all.

All other parameters arrive intact however, so it is simply a matter of accessing this File.


Solution

  • The Java tutorial on HowToGraphQL is sadly woefully outdated. I'm working on updating it (I'm the author of the original), but it takes time. The current best practice is to directly use graphql-java's RuntimeWiring when working in schema-first manner*. There's some basic docs here: http://graphql-java.readthedocs.io/en/v6/schema.html

    Regardless, the way to handle file uploads has not changed: stick the entire HttpServletRequestobject, or just the binary part, into the shared context:

    graphQL.execute(ExecutionInput.newExecutionInput()
        .query(operation)
        .context(request.getPart("file")) //add the binary to the context
        .build());
    

    Later on, in the DataFetcher, you can get a hold of it via: DataFetchingEnvironment#getContext:

    Part file = environment.getContext();
    file.getInputStream(); //do something with the binary content
    

    You could, of course, use some other (dynamic) structure as the context, instead of the binary directly.

    This side-loading way of handling uploads is a result of using JSON as the transport format, and JSON is textual. Same reason why file downloads are normally handled outside of GraphQL (where the response only contains and ID or a URL needed to initiate the download). The reference server implementation includes examples using the same strategy.
    At the moment, there's no non-JSON GraphQL clients, as far as I know.

    *For code-first, graphql-spqr (my project) remains the most convenient way.