FastAPI cookbook · Intermediate

Upload files directly to S3 from a FastAPI app

Let clients upload large files without streaming them through your API servers.

Approach

Your API authorizes the upload and returns a pre-signed POST (or PUT) for S3 with a generated key, content-type and size conditions, valid for a few minutes. The client uploads directly to S3, then notifies your API, which verifies the object (size/type, optional virus scan) before using it.

Solution

import uuid
import boto3
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel, Field

s3 = boto3.client("s3", region_name="eu-west-1")
BUCKET = "acme-uploads"
ALLOWED = {"image/png", "image/jpeg", "application/pdf"}
router = APIRouter(prefix="/uploads")


class UploadRequest(BaseModel):
    filename: str = Field(max_length=200)
    content_type: str
    size: int = Field(gt=0, le=50 * 1024 * 1024)


@router.post("")
async def create_upload(body: UploadRequest, user: CurrentUser):
    if body.content_type not in ALLOWED:
        raise HTTPException(415, "Unsupported file type")
    key = f"users/{user.id}/{uuid.uuid4().hex}"
    post = s3.generate_presigned_post(
        Bucket=BUCKET, Key=key,
        Fields={"Content-Type": body.content_type},
        Conditions=[{"Content-Type": body.content_type}, ["content-length-range", 1, body.size]],
        ExpiresIn=300,
    )
    return {"key": key, "url": post["url"], "fields": post["fields"]}


@router.post("/{key:path}/complete")
async def complete_upload(key: str, user: CurrentUser, db: DB):
    if not key.startswith(f"users/{user.id}/"):
        raise HTTPException(403, "Not your upload")
    head = s3.head_object(Bucket=BUCKET, Key=key)       # verify it exists and check size/type
    await files.register(db, user.id, key, head["ContentLength"], head["ContentType"])
    return {"status": "ok"}

Pitfalls

  • Letting clients choose the object key (overwrites, path tricks).
  • Long-lived presigned URLs.
  • Calling blocking boto3 in async endpoints for heavy operations (use run_in_threadpool or aioboto3).

Variations

  • Multipart uploads for very large files.
  • Trigger a Lambda/worker on ObjectCreated to scan and generate thumbnails.

Further reading: boto3: Presigned URLs

Reference: https://boto3.amazonaws.com/v1/documentation/api/latest/guide/s3-presigned-urls.html

More FastAPI recipes