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 theUploadingNetworkTransport
protocol on yourApolloClient
instance. If you're usingRequestChainNetworkTransport
(which is set up by default), this protocol is already supported.The correct
MIME
type for the data you're uploading. The default value isapplication/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:
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:
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:
GraphQL1mutation 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:
GraphQL1# 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
String
s passed into the query must be the same number as the array of files.