Skip to content

Commit

Permalink
Move file uploads doc to its own page (#98)
Browse files Browse the repository at this point in the history
  • Loading branch information
Meschreiber authored and gh-action-runner committed Oct 25, 2023
1 parent 1a055d8 commit be73682
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 88 deletions.
11 changes: 6 additions & 5 deletions docs/source/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@
"The Codegen CLI": "/code-generation/codegen-cli",
"Configuration": "/code-generation/codegen-configuration",
"Downloading a Schema": "/code-generation/downloading-schema",
"Running Code Generation in Swift Code": "/code-generation/run-codegen-in-swift-code"
"Running Code Generation in Swift Code": "/code-generation/run-codegen-in-swift-code",
"Code Generation Troubleshooting": "/troubleshooting/codegen-troubleshooting"
},
true
],
Expand Down Expand Up @@ -93,8 +94,7 @@
],
"Networking": [
{
"Creating a Client": "/networking/client-creation",
"Advanced Network Configuration": "/networking/request-pipeline"
"Creating a Client": "/networking/client-creation"
},
true
],
Expand All @@ -104,9 +104,10 @@
},
true
],
"Troubleshooting": [
"Advanced": [
{
"Code Generation": "/troubleshooting/codegen-troubleshooting"
"File Uploads": "/file-uploads",
"Request Chain Customization": "/networking/request-pipeline"
},
true
]
Expand Down
81 changes: 0 additions & 81 deletions docs/source/fetching/mutations.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,84 +96,3 @@ When people talk about GraphQL, they often focus on the data fetching side of th
In GraphQL, mutations can return any type, and that type can be queried just like a regular GraphQL query. So the question is - what type should a particular mutation return?

In most cases, the data available from a mutation result should be the server developer's best guess of the data a client would need to understand what happened on the server. For example, a mutation that creates a new comment on a blog post might return the comment itself. A mutation that reorders an array might need to return the whole array.

## Uploading files

Apollo iOS supports file uploads via the [GraphQL multipart request specification](https://github.com/jaydenseric/graphql-multipart-request-spec) 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](https://www.apollographql.com/blog/backend/file-uploads/file-upload-best-practices/) for the advantages and disadvantages of multiple approaches.
- The [Apollo Router](/router/) doesn't support `multipart-form` uploads.

### Uploading Directly With Apollo

At the moment, we only support uploads for a single operation, not for batch operations. You can upload multiple files for a single operation if your server supports it, though.

To upload a file, you will 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
mutation UploadFile($file:Upload!) {
singleUpload(file:$file) {
id
}
}
```

If you wanted to use this to upload a file called `a.txt`, it would look something like this:

```swift
// Create the file to upload
guard let fileURL = Bundle.main.url(forResource: "a", withExtension: "txt"),
let file = GraphQLFile(
fieldName: "file", // Must be the name of the field the file is being uploaded to
originalName: "a.txt",
mimeType: "text/plain", // <-defaults to "application/octet-stream"
fileURL: fileURL
) else {
// Either the file URL couldn't be created or the file couldn't be created.
return
}

// Actually upload the file
client.upload(
operation: UploadFileMutation(file: "a"), // <-- `Upload` is a custom scalar that's a `String` under the hood.
files: [file]
) { result in
switch result {
case .success(let graphQLResult):
print("ID: \(graphQLResult.data?.singleUpload.id)")
case .failure(let error):
print("error: \(error)")
}
}
```

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
mutation AvatarUpload($userID: GraphQLID!, $file: Upload!) {
id
}
```

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

```graphql
# Assumes AvatarObject(userID: GraphQLID, file: Upload) exists
mutation AvatarUpload($avatarObject: AvatarObject!) {
id
}
```

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.
83 changes: 83 additions & 0 deletions docs/source/file-uploads.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
---
title: File uploads
description: Enabling file uploads in Apollo Client for iOS
---

Apollo iOS supports file uploads via the [GraphQL multipart request specification](https://github.com/jaydenseric/graphql-multipart-request-spec) 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](https://www.apollographql.com/blog/backend/file-uploads/file-upload-best-practices/) for the advantages and disadvantages of multiple approaches.
- The [Apollo Router](/router/) doesn't support `multipart-form` 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
mutation UploadFile($file:Upload!) {
singleUpload(file:$file) {
id
}
}
```

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

```swift
// Create the file to upload
guard let fileURL = Bundle.main.url(forResource: "a", withExtension: "txt"),
let file = GraphQLFile(
fieldName: "file", // Must be the name of the field the file is being uploaded to
originalName: "a.txt",
mimeType: "text/plain", // <-defaults to "application/octet-stream"
fileURL: fileURL
) else {
// Either the file URL couldn't be created or the file couldn't be created.
return
}

// Actually upload the file
client.upload(
operation: UploadFileMutation(file: "a"), // <-- `Upload` is a custom scalar that's a `String` under the hood.
files: [file]
) { result in
switch result {
case .success(let graphQLResult):
print("ID: \(graphQLResult.data?.singleUpload.id)")
case .failure(let error):
print("error: \(error)")
}
}
```

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
mutation AvatarUpload($userID: GraphQLID!, $file: Upload!) {
id
}
```

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

```graphql
# Assumes AvatarObject(userID: GraphQLID, file: Upload) exists
mutation AvatarUpload($avatarObject: AvatarObject!) {
id
}
```

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.
4 changes: 2 additions & 2 deletions docs/source/networking/request-pipeline.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Advanced networking configuration
description: Customizing the Apollo iOS Request Chain
title: Request Chain Customization
description: Learn how to customize the Apollo iOS request chain via custom interceptors
---

In Apollo iOS, the [`ApolloClient`](https://www.apollographql.com/docs/ios/docc/documentation/apollo/apolloclient) uses a [`NetworkTransport`](https://www.apollographql.com/docs/ios/docc/documentation/apollo/networktransport) object to fetch GraphQL queries from a remote GraphQL server.
Expand Down

0 comments on commit be73682

Please sign in to comment.