2022-04-15 10:00:00+00:00

Uploading files over GraphQL is a source of debate. While some architectures advocate for separate REST upload endpoints or direct S3 presigned URLs, keeping files within the GraphQL mutation stream simplifies transactional workflows. The standard solution is to implement the GraphQL Multipart Request Specification.

Using Ariadne, we configure a custom Upload scalar that processes incoming file streams asynchronously without choking memory.


1. Setting Up the Upload Resolver

We declare the Upload scalar in our schema and map it to Ariadne's upload handler. The uploaded file arrives as a FastAPI UploadFile instance, which provides asynchronous file read routines:

# Resolving File Upload Mutation in Ariadne
from ariadne import MutationType, upload_scalar

mutation = MutationType()

@mutation.field("uploadDocument")
async def resolve_upload_document(obj, info, file):
    # 'file' is parsed as a fastapi.UploadFile
    filename = file.filename
    content_type = file.content_type
    
    # Read chunk by chunk asynchronously to protect RAM
    content = await file.read()
    
    # Process or store file (e.g. upload to local directory or MinIO/S3)
    await save_to_storage(filename, content)
    return {"success": True, "filename": filename}

# Register the upload scalar in Ariadne
schema_binds = [mutation, upload_scalar]

2. Performance Safeguards

Because processing huge files in memory can lead to Out-Of-Memory (OOM) crashes, we enforce file size validations at the gateway level (api-gateway), restricting mutation payloads to a maximum of 10MB.