Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move file uploads doc to its own page #98

Merged
merged 3 commits into from
Oct 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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": [
Meschreiber marked this conversation as resolved.
Show resolved Hide resolved
{
"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
Loading