Python 3 min read

FastAPI Tutorial Part 14: File Uploads and Storage

Handle file uploads in FastAPI. Learn form data, file validation, cloud storage integration with S3, and serving static files.

MR

Moshiour Rahman

Advertisement

Basic File Upload

from fastapi import FastAPI, File, UploadFile

app = FastAPI()

@app.post("/upload")
async def upload_file(file: UploadFile = File(...)):
    contents = await file.read()
    return {
        "filename": file.filename,
        "content_type": file.content_type,
        "size": len(contents)
    }

File Validation

from fastapi import HTTPException

ALLOWED_TYPES = ["image/jpeg", "image/png", "image/gif"]
MAX_SIZE = 5 * 1024 * 1024  # 5MB

@app.post("/upload/image")
async def upload_image(file: UploadFile = File(...)):
    if file.content_type not in ALLOWED_TYPES:
        raise HTTPException(400, "Invalid file type")

    contents = await file.read()
    if len(contents) > MAX_SIZE:
        raise HTTPException(400, "File too large")

    # Save file
    file_path = f"uploads/{file.filename}"
    with open(file_path, "wb") as f:
        f.write(contents)

    return {"path": file_path}

Multiple Files

from typing import List

@app.post("/upload/multiple")
async def upload_multiple(files: List[UploadFile] = File(...)):
    results = []
    for file in files:
        contents = await file.read()
        results.append({
            "filename": file.filename,
            "size": len(contents)
        })
    return {"files": results}

Form Data with Files

from fastapi import Form

@app.post("/upload/profile")
async def upload_profile(
    name: str = Form(...),
    email: str = Form(...),
    avatar: UploadFile = File(...)
):
    # Save avatar
    path = f"avatars/{avatar.filename}"
    with open(path, "wb") as f:
        content = await avatar.read()
        f.write(content)

    return {
        "name": name,
        "email": email,
        "avatar_path": path
    }

AWS S3 Integration

import boto3
from botocore.exceptions import ClientError
import uuid

s3_client = boto3.client(
    "s3",
    aws_access_key_id="YOUR_KEY",
    aws_secret_access_key="YOUR_SECRET",
    region_name="us-east-1"
)

BUCKET_NAME = "my-bucket"

@app.post("/upload/s3")
async def upload_to_s3(file: UploadFile = File(...)):
    file_key = f"uploads/{uuid.uuid4()}-{file.filename}"

    try:
        s3_client.upload_fileobj(
            file.file,
            BUCKET_NAME,
            file_key,
            ExtraArgs={"ContentType": file.content_type}
        )

        url = f"https://{BUCKET_NAME}.s3.amazonaws.com/{file_key}"
        return {"url": url, "key": file_key}

    except ClientError as e:
        raise HTTPException(500, f"Upload failed: {str(e)}")

Serving Static Files

from fastapi.staticfiles import StaticFiles

app.mount("/static", StaticFiles(directory="static"), name="static")
app.mount("/uploads", StaticFiles(directory="uploads"), name="uploads")

Complete Example

from fastapi import FastAPI, File, UploadFile, HTTPException
from typing import List
import os
import uuid
import aiofiles

app = FastAPI()

UPLOAD_DIR = "uploads"
os.makedirs(UPLOAD_DIR, exist_ok=True)

ALLOWED_EXTENSIONS = {".jpg", ".jpeg", ".png", ".gif", ".pdf"}
MAX_FILE_SIZE = 10 * 1024 * 1024

def validate_file(file: UploadFile):
    ext = os.path.splitext(file.filename)[1].lower()
    if ext not in ALLOWED_EXTENSIONS:
        raise HTTPException(400, f"File type {ext} not allowed")

async def save_file(file: UploadFile) -> str:
    validate_file(file)

    ext = os.path.splitext(file.filename)[1]
    unique_name = f"{uuid.uuid4()}{ext}"
    file_path = os.path.join(UPLOAD_DIR, unique_name)

    async with aiofiles.open(file_path, "wb") as f:
        content = await file.read()
        if len(content) > MAX_FILE_SIZE:
            raise HTTPException(400, "File too large")
        await f.write(content)

    return unique_name

@app.post("/files/upload")
async def upload_file(file: UploadFile = File(...)):
    filename = await save_file(file)
    return {"filename": filename, "url": f"/uploads/{filename}"}

@app.post("/files/upload-multiple")
async def upload_multiple(files: List[UploadFile] = File(...)):
    results = []
    for file in files:
        filename = await save_file(file)
        results.append({"filename": filename})
    return {"files": results}

Summary

FeatureMethod
Single fileUploadFile = File(...)
Multiple filesList[UploadFile] = File(...)
With form dataCombine Form() and File()
Cloud storageboto3 for S3

Next Steps

In Part 15, we’ll explore Testing FastAPI Applications - writing comprehensive tests for your API.

Series Navigation:

  • Part 1-13: Previous parts
  • Part 14: File Uploads (You are here)
  • Part 15: Testing

Advertisement

MR

Moshiour Rahman

Software Architect & AI Engineer

Share:
MR

Moshiour Rahman

Software Architect & AI Engineer

Enterprise software architect with deep expertise in financial systems, distributed architecture, and AI-powered applications. Building large-scale systems at Fortune 500 companies. Specializing in LLM orchestration, multi-agent systems, and cloud-native solutions. I share battle-tested patterns from real enterprise projects.

Related Articles

Comments

Comments are powered by GitHub Discussions.

Configure Giscus at giscus.app to enable comments.