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

Ask Community