GraphQL File Upload with Angular

November 20, 2019

banner

GraphQL does not support file upload natively. Thankfully, jaydenseric created graphql-multipart-request-spec to standardize the format to use multipart (HTTP file upload) request to upload file to a GraphQL server. On the server side, most popular GQL servers support this standard. On the client side, you can use Apollo GraphQL client.

However, what if you want to implement your own file upload client? This article shows that all you need to do is to create a multipart request according to the format of graphql-multipart-request-spec and send it to the server. It uses Angular as the frontend framework, however, you can use whatever as long as you follow the spec.

Structure of graphql-multipart-request-spec

First let’s review the spec. I strongly recommend you read the spec on it github page.

The most important parts are here, for a single file upload, you will need to create 3 parts in the request:

  1. operations: A JSON encoded operations object with files replaced with null. Example:

    {
      query: `
        mutation($file: Upload!) {
          singleUpload(file: $file) {
            id
          }
        }
      `,
      variables: {
        file: null
      }
    }
    
  2. map: A JSON encoded map of where files occurred in the operations. For each file, the key is the file multipart form field name and the value is an array of operations paths. Example:

    {
      file: ["variables.file"]
    }
    
  3. File fields: Each file extracted from the operations object with a unique, arbitrary field name.

Once you have these three parts, you need to create your multipart request in exactly the same order of: operations, map, and File fields.

Client Implementation with Angular FormData

In Angular we use FormData to create a multipart request.

Template

The HTML file has a simple file input field:

<input #fileInput type="file" (change)="upload($event)">

It will call upload($event) when a user selects a file. The $event.target.files will contain the file object of the selected file.

Typescript

Using the example in the last section, first we need to create the operations part:

upload($event) {
var operations = {
  query: `
    mutation($file: Upload!) {
      singleUpload(file: $file) {
        id
      }
    }
  `,
  variables: {
    file: null
  }
}

Then we create the map part (I use _map to avoid conflicting with Rxjs’s map operator):

var _map = {
  file: ["variables.file"]
}

At last, we create a FormData to add these two parts and the last file part together:

var file = $event.target.files[0]
var fd = new FormData()
fd.append('operations', JSON.stringify(operations))
fd.append('map', JSON.stringify(_map))
fd.append('file', file, file.name)

Notice in the last step, the three parts have to be in that order. Also notice that the key in the map is the same as the file name in the third part: in our example, both are called file.

Now all we need to do is to post this multipart request to our GQL server:

this.http.post(this.gqlUrl, fd).subscribe()

File upload progress

If you want to track the upload progress, add the options to the post request:

this.http.post(this.gqlUrl, fd, {
  reportProgress: true,
  observe: events
}).subscribe()

That is all! Now you know how to upload file in your Angular App to a GraphQL server. Happy coding!

GraphQL Angular File upload