File Uploads
The gateway automatically supports GraphQL file uploads via multipart requests, following the GraphQL multipart request specification.
Proto Definition
Map bytes fields to handle file uploads:
message UploadAvatarRequest {
string user_id = 1;
bytes avatar = 2; // Maps to Upload scalar in GraphQL
}
message UploadAvatarResponse {
string user_id = 1;
int64 size = 2;
}
service UserService {
rpc UploadAvatar(UploadAvatarRequest) returns (UploadAvatarResponse) {
option (graphql.schema) = {
type: MUTATION
name: "uploadAvatar"
request { name: "input" }
};
}
}
GraphQL Mutation
The generated GraphQL schema includes an Upload scalar:
mutation UploadAvatar($file: Upload!) {
uploadAvatar(input: { userId: "123", avatar: $file }) {
userId
size
}
}
Using curl
curl http://localhost:8888/graphql \
--form 'operations={"query": "mutation($file: Upload!) { uploadAvatar(input:{userId:\"123\", avatar:$file}) { userId size } }", "variables": {"file": null}}' \
--form 'map={"0": ["variables.file"]}' \
--form '0=@avatar.png'
Request Format
- operations - JSON containing the query and variables
- map - Maps file indices to variable paths
- 0, 1, … - The actual file content
JavaScript Client
Using Apollo Client with apollo-upload-client:
import { createUploadLink } from 'apollo-upload-client';
import { ApolloClient, InMemoryCache } from '@apollo/client';
const client = new ApolloClient({
link: createUploadLink({ uri: 'http://localhost:8888/graphql' }),
cache: new InMemoryCache(),
});
// Upload mutation
const UPLOAD_AVATAR = gql`
mutation UploadAvatar($file: Upload!) {
uploadAvatar(input: { userId: "123", avatar: $file }) {
userId
size
}
}
`;
// Trigger upload
const file = document.querySelector('input[type="file"]').files[0];
client.mutate({
mutation: UPLOAD_AVATAR,
variables: { file },
});
Multiple Files
Upload multiple files by adding more entries to the map:
curl http://localhost:8888/graphql \
--form 'operations={"query": "mutation($files: [Upload!]!) { uploadFiles(files: $files) { count } }", "variables": {"files": [null, null]}}' \
--form 'map={"0": ["variables.files.0"], "1": ["variables.files.1"]}' \
--form '0=@file1.pdf' \
--form '1=@file2.pdf'
File Size Limits
By default, uploads are limited by your web server configuration. For large files, consider:
- Streaming uploads to avoid memory pressure
- Setting appropriate timeouts
- Using a CDN or object storage for very large files
Backend Handling
On the gRPC backend, the file is received as bytes. Example in Rust:
async fn upload_avatar(
&self,
request: Request<UploadAvatarRequest>,
) -> Result<Response<UploadAvatarResponse>, Status> {
let req = request.into_inner();
let file_data = req.avatar; // Vec<u8>
let size = file_data.len() as i64;
// Save file, upload to S3, etc.
Ok(Response::new(UploadAvatarResponse {
user_id: req.user_id,
size,
}))
}