File Uploads and Static Files
Introduction
APIs accept multipart file uploads for avatars, documents, and imports. FastAPI uses UploadFile for streaming uploads and StaticFiles to serve assets. This chapter validates uploads, saves safely, and notes production patterns with Nginx or object storage.
Prerequisites
- Path, Query, and Request Body
pip install python-multipart
Single File Upload
Code explanation:
UploadFilestreams data—useawait file.read()in async routes- Sync route:
file.file.read()without await
Test:
curl -X POST http://127.0.0.1:8000/upload \
-F "file=@photo.jpg"Save to Disk
Code explanation:
- Do not trust client
filenamefor path—generateuuidname - Store original name in database if needed
Compare Flask uploads.
Warning
Validate Content, Not Just Extension
Malware can rename extensions—inspect magic bytes in production.
File + Form Fields
from fastapi import Form
@app.post("/upload/meta")
async def upload_with_meta(
title: str = Form(...),
file: UploadFile = File(...),
):
return {"title": title, "filename": file.filename}Requires python-multipart.
Multiple Files
from typing import Annotated
@app.post("/upload/multi")
async def upload_multiple(files: list[UploadFile] = File(...)):
return [{"filename": f.filename, "size": f.size} for f in files]Download With FileResponse
from fastapi.responses import FileResponse
@app.get("/download/{stored_name}")
def download(stored_name: str):
path = UPLOAD_DIR / stored_name
if not path.is_file():
raise HTTPException(status_code=404, detail="Not found")
return FileResponse(path, filename=stored_name, media_type="application/octet-stream")Protect with Depends(get_current_user)—do not expose upload directory listing publicly.
Static Files Mount
from fastapi.staticfiles import StaticFiles
app.mount("/static", StaticFiles(directory="static"), name="static")Template reference (if using Jinja2 optionally):
<link rel="stylesheet" href="/static/css/style.css">Production: serve /static via Nginx instead of Python process.
Settings for Upload Limits
Starlette/FastAPI body size—configure at server or reverse proxy:
# Concept: limit in middleware or nginx client_max_body_sizeNginx:
client_max_body_size 10M;Match application MAX_BYTES check.
Object Storage (Concept)
Client → FastAPI validates → S3 API put_object → DB stores object key
Client ← presigned URL ← FastAPISame validation rules; offload bytes to S3/OSS/MinIO.
Async Write Pattern
For large files, stream to disk:
import shutil
@app.post("/upload/stream")
async def stream_upload(file: UploadFile = File(...)):
dest = UPLOAD_DIR / f"{uuid.uuid4().hex}.bin"
with dest.open("wb") as buffer:
shutil.copyfileobj(file.file, buffer)
return {"saved": dest.name}FAQ
422 on upload?
Missing multipart/form-data or wrong field name file.
UploadFile sync route?
Use def route and read file.file synchronously.
Memory spike on read()?
Stream in chunks for large files instead of read() entire body.
Authenticated uploads?
Add Depends(get_current_user) to route.
Virus scan?
Queue scan job after save—background tasks chapter.
Web static vs API same app?
Mount StaticFiles on subpath; API under /api/v1.