File Upload With GraphQL from a React Application

Enes Palaz
8 min readAug 16, 2019

--

sd
File upload in React front end can be implemented with Apollo Upload Client

GraphQL enables us developers to implement a powerful and flexible abstraction layer between front-end applications and REST APIs. As an intern who got introduced to GraphQL only 3 months ago, I read many articles, blog posts and guides prepared by members of GraphQL community on how to build GraphQL services and connect it to React applications. But none of these resources mentioned uploading a file was possible with GraphQL.

Can we say this for file uploads?? Maybe…

When I was tasked with managing and building a new feature that included implementing a file upload using GraphQL, I realized that although this feature was included in Apollo Server 2.0, most of the resources on this feature were laid out in various repositories and guides left out important steps.

I started my research by reading official blog post about the feature and religiously followed it steps but unfortunately it was my entrance to the sea of cryptic errors simply because the blog post was not complete. I spent nearly a week jumping through github repositories for GraphQL file upload features to figure what was wrong with my code.

Therefore I decided to gather this guide to show necessary steps to implement a basic file upload capable GraphQL server and React Client by using GraphQL mutations. Also, talk about my experience with this feature of GraphQL and why as a company we decided to choose a different method.

In the following section, this guide will take you through necessary steps to create a GraphQL server that can handle file upload mutations sent from a React application with Apollo Client. GraphQL server is going to have 2 different capabilities; saving file into file system and streaming it into an S3 bucket.

Basic Architecture

Diagram of all of the parts, for more detail check out the github repository.

In this guide, our system will consist of:
1. React application with Apollo Client package
2. GraphQL server that uses Apollo Server package

Our GraphQL server will have capabilities to save uploaded files into a file system and stream received streams into a S3 Bucket on AWS. For this, we will have 2 queries; singleUpload and singleUploadStream.

Step 1: Preparing GraphQL Server

Setup

To create a GraphQL server with apollo-server library, it is easiest to follow its official guide but I will still list the required steps here.
Start by creating a directory and installing packages:

$ mkdir your_dir_name
$ cd your_dir_name
$ yarn init --yes
$ yarn add apollo-server graphql grapql-upload
$ yarn add aws-sdk

This set of command should install packages and create. Next step is starting to define out type definitions and resolvers.

Type Definitions and Mutation Resolvers

Type definitions for mutations and a filler query

We have 2 different mutations that we defined. singleUpload mutation is for uploading a file and saving it into a directory in the file system while singleUploadStream is used for streaming to a S3 bucket. These mutations take Upload scalar type that comes as default in the Apollo Server 2.0 so its resolution is done by the apollo server itself. Apollo Server maps multipart request form to this Upload scalar and generate a promise for the file. Both of these mutation return File type that consists of filename, mimetype and encoding fields, while this doesn’t fit into a real life schedule but return of correct fields for a file indicates proper uploading of the file to the GraphQL server.

Resolvers for singleUpload and singleUploadStream mutations

A key detail in these file upload resolvers is the promise that is returned by the file argument. In order to be able read file stream, we should wait for promise to be resolved. In this example both mutation resolvers use different methods. singleUpload returns a promise by using then() and saving resolved stream into the filesystem. On the other hand, singleUploadStream uses await to wait for the promise resolution to upload file to S3 by using file stream. Another difference between these resolvers is that using await inside the resolver function requires it to be defined as async while singleUpload resolver can stay synchronous.

Note: Old examples can show file as having a filed stream, instead of a createReadStream function. Check graphql-upload repository for latest changes. PR for the change to understand background.

Configure the Server and AWS SDK

This server.js file contains code to start Apollo Server on port 4000 of the localhost and configure AWS JavaScript SDK (refer to documentation for further details). Creating ApolloServer instance with the type definitions and mutation resolvers should be enough to handle file uploads for saving in filesystem and uploading to S3. If you need further help on creating S3 Buckets on AWS, refer to this link.

You can test the GraphQL server by typing

$ cd your_project_dir
$ node server.js

into your console. This should start the server on port 4000 of localhost. After this type localhost:4000 into your browser and you should be able to see the playground page on which you can send queries and mutations to server for testing and also see auto generated documentation based on files.

Apollo Server playground screenshot showing defined mutations and queries.

Now, you can verify that your mutations and queries are recognized by the server and you are ready to send graphql requests to the server from your React Application.

Step 2: Creating the React Application

In order to create our react application and its boilerplate code easily we will make user create-react-app tool developed by Facebook.

$ yarn create react-app your-app-name
$ yarn add apollo-client apollo-upload-client react-apollo graphql-tag

This command should create a react application directory with given name and autogenerate the boilerplate code to start developing our application. You can test if this command worked successfully by typing

$ yarn start

This should start a development server on port localhost:3000 with live update.

Sample application created by create-react-app.

If you can see this screen, it means that your project is set successfully and you can start editing this template to upload your files.

Configure Apollo Client and Prepare Schemas for Mutations

First step to initialize ApolloClient is creating an apolloCache instance and ApolloLink instance to specify server address. In order to successfully upload files, you have to use createUploadLink instead of HttpLink, this is subtle but important details that is missing in official documentation.

It is time to write mutations to use in Mutation components from react-apollo library.

This code creates a basic UI with 2 forms one for singleUpload and one for singleUploadStream. In this example, onChange event is used to fire file upload mutation functions but you can chose to fire it with event of your choice. Doing file upload onChange enables you to upload file as soon as it is selected. Another detail here is having encType of the form element as multipart/form-data. Check this link to read about GraphQL multipart request specifications for more details. After setting the render function with these components your UI should look somewhat similar to this.

UI of the example React application

Step 3: Testing and Uploading Filessss!

Testing File System Save
Before testing this be sure both the server and react application is up and running. If not start them by following step 1 and 2. Let’s try uploading a file with singleUpload mutation and see it saved in servers file system.

If you check the console, you should be able to see information of uploaded file returned by the GraphQL server. This means our upload was successful and only thing left is to verify that server saved file into filesystem.

Aaand voila, “no-image.png” file is successfully uploaded and saved into the file system.

Testing S3 Uploads
Before testing streaming uploaded files to S3 bucket, it is important to check credentials.json file contains right accessKeyId, secretAccessKey and region values in it with proper access to S3 and particular bucket that you are trying to upload too.

To test this, you will follow the same steps as singleUpload mutation but is difference is server will print S3 upload result into the terminal so you can verify it is successfully uploaded to S3 bucket and can also check its location to use if you desire. If your credentials and configuration is right you should see this in your terminal.

Upload log of successfully uploaded file to S3

You can also verify by checking S3 bucket on AWS console. Now, you have GraphQL server that can save and upload files to S3 but is it the right choice for you?

Ending with a plot twist…

Even though implementing all data fetches by using GraphQL looks really useful and versatile on paper, there are some questions for relatively new features like file upload that developers need to consider. During my implementation of this feature in Vida, I had to answer these questions and make my choices accordingly.

  1. How GraphQL file upload compares to using a REST API call with fetch or XMLHttpRequest?
  2. How much upload traffic that my system is going to have?
  3. Where is the uploaded file streamed and from where? (to S3 from GQL server or maybe from another webserver)

Let’s start with the first one. My experience show that GraphQL file upload gives you less control to you than fetch and XHR requests. Advantage of the fetch compared to GraphQL upload is the speed, assuming that your application already fetches and patches data to REST endpoints, patching a new endpoint by using libraries like request and formData makes it easier than making changes on a GraphQL server that wasn’t setup for file upload at first place. In addition to this, XHR requests provides a callback for tracking the progress of the file upload if one needs to implement a progress bar for file upload like I had in my feature specifications.

Second and third question comes into frame when you think about keeping your GraphQL server safe because it is a single gateway for your frontend applications to reach data that you have available elsewhere. My requirements included streaming these uploaded files from GraphQL server to another webserver to be uploaded to S3 from there. Therefore, I couldn’t find any reason to stream files through GraphQL since it wouldn’t add any additional benefit while keeping server under heavy load of streaming files from one point to another.

At the end, my decision was benefits of GraphQL for file upload compared to other methods was minimal. Even though I spent researching file upload with GraphQL for hours by using various libraries, I decided to go with an XHR request to provide user with as much context as possible.

I suggest anybody who wants to use GraphQL for file upload to think about their requirements. I believe with future features, GraphQL is going to be a viable options for uploading files. But for now, it needs the help of community for new features.

--

--