File uploads

Enabling file uploads in Apollo Client for iOS


Apollo iOS supports file uploads via the GraphQL multipart request specification with a few caveats:

  • Uploading files with GraphQL is most often suitable for proof-of-concept applications. In production, using purpose-built tools for file uploads may be preferable. Refer to this blog post for the advantages and disadvantages of multiple approaches.

  • Neither the Apollo Router Core nor GraphOS Router support multipart/form-data uploads.

Uploading files with Apollo iOS

Apollo iOS only supports uploads for a single operation, not for batch operations. You can upload multiple files with a single operation if your server supports it, though.

To upload a file, you need:

  • A NetworkTransport which also supports the UploadingNetworkTransport protocol on your ApolloClient instance. If you're using RequestChainNetworkTransport (which is set up by default), this protocol is already supported.

  • The correct MIME type for the data you're uploading. The default value is application/octet-stream.

  • Either the data or the file URL of the data you want to upload.

  • A mutation which takes an Upload as a parameter. Note that this must be supported by your server.

Here is an example of a GraphQL query for a mutation that accepts a single upload, and then returns the id for that upload:

GraphQL
1mutation UploadFile($file:Upload!) {
2    singleUpload(file:$file) {
3        id
4    }
5}

If you want to use this to upload a file called a.txt, it looks something like this:

Swift
1// Create the file to upload
2guard let fileURL = Bundle.main.url(forResource: "a", withExtension: "txt"),
3      let file = GraphQLFile(
4        fieldName: "file", // Must be the name of the field the file is being uploaded to
5        originalName: "a.txt",
6        mimeType: "text/plain", // <-defaults to "application/octet-stream"
7        fileURL: fileURL
8) else {
9  // Either the file URL couldn't be created or the file couldn't be created.
10  return
11}
12
13// Actually upload the file
14client.upload(
15  operation: UploadFileMutation(file: "a"), // <-- `Upload` is a custom scalar that's a `String` under the hood.
16  files: [file]
17) { result in
18  switch result {
19  case .success(let graphQLResult):
20    print("ID: \(graphQLResult.data?.singleUpload.id)")
21  case .failure(let error):
22    print("error: \(error)")
23  }
24}

A few other notes:

  • Due to some limitations around the spec, whatever the file is being added for should be at the root of your GraphQL query. So if you have something like:

    GraphQL
    1mutation AvatarUpload($userID: GraphQLID!, $file: Upload!) {
    2  id
    3}

    it will work, but if you have some kind of object encompassing both of those fields like this:

    GraphQL
    1# Assumes AvatarObject(userID: GraphQLID, file: Upload) exists
    2mutation AvatarUpload($avatarObject: AvatarObject!) {
    3  id
    4}

    it will not. Generally you should be able to deconstruct upload objects to allow you to send the appropriate fields.

  • If you are uploading an array of files, you need to use the same field name for each file. These will be updated at send time.

  • If you are uploading an array of files, the array of Strings passed into the query must be the same number as the array of files.

Feedback

Edit on GitHub

Forums