FastAPI File Uploads: A Complete Guide from Basics to Advanced

In modern web development, file upload is a common requirement. As a high-performance and easy-to-use Python web framework, FastAPI provides a concise and efficient solution for file uploads. This article will explain how to handle file uploads with FastAPI from basic to advanced, helping you quickly master the core skills through examples.

1. Basic Preparation: Installation and Configuration

Before starting, ensure you have installed the necessary dependencies:

pip install fastapi uvicorn
  • FastAPI: The core framework providing file upload-related APIs.
  • Uvicorn: An ASGI server for running FastAPI applications.

2. Basic File Upload: Single File

2.1 Core Concept

FastAPI handles file uploads using the UploadFile class, which is more powerful than the traditional bytes type. It supports metadata such as filename, file type, and file size.

2.2 Example Code

from fastapi import FastAPI, UploadFile, File
from fastapi.responses import JSONResponse

app = FastAPI(title="File Upload Example")

@app.post("/upload")
async def upload_single_file(file: UploadFile = File(...)):
    """
    Upload a single file (using UploadFile)
    """
    try:
        # Read file content (suitable for small files)
        file_content = await file.read()
        # Save file to local directory (e.g., uploads folder)
        with open(f"uploads/{file.filename}", "wb") as f:
            f.write(file_content)

        return {
            "status": "success",
            "filename": file.filename,
            "file_type": file.content_type,
            "size": len(file_content)
        }
    except Exception as e:
        return JSONResponse(status_code=500, content={"message": f"Upload failed: {str(e)}"})
    finally:
        await file.close()  # Ensure file is closed to release resources

2.3 Code Explanation

  • UploadFile = File(...): Declare the file parameter, where ... indicates a required field (automatically prompted in Swagger UI).
  • await file.read(): Asynchronously read file content into memory (recommended for small files; large files should use streaming processing).
  • file.filename: Get the original filename uploaded by the client.
  • file.content_type: Get the file MIME type (e.g., image/png).
  • file.size: Get the file size in bytes (supported in FastAPI 0.90+).

2.4 Testing the API

Run the application:

uvicorn main:app --reload  # main.py is the filename, app is the FastAPI instance

Access the Swagger UI (auto-generated) at http://localhost:8000/docs. On the /upload endpoint, click “Try it out”, select a local file, and test.

3. Advanced File Upload: Multiple Files and Complex Scenarios

3.1 Multiple File Uploads

FastAPI supports uploading multiple files simultaneously by defining the parameter as List[UploadFile]:

@app.post("/upload-multiple")
async def upload_multiple_files(files: list[UploadFile] = File(...)):
    """
    Upload multiple files
    """
    result = []
    for file in files:
        file_content = await file.read()
        with open(f"uploads/{file.filename}", "wb") as f:
            f.write(file_content)
        result.append({
            "filename": file.filename,
            "size": len(file_content)
        })
    return {"files": result, "count": len(files)}

3.2 Upload with Additional Form Data

To upload files with extra information (e.g., user ID, description), use Form parameters:

from fastapi import Form

@app.post("/upload-with-data")
async def upload_with_data(
    user_id: int = Form(...),  # Additional form data
    description: str = Form(""),  # Optional field
    file: UploadFile = File(...)  # File
):
    file_content = await file.read()
    return {
        "user_id": user_id,
        "description": description,
        "filename": file.filename
    }

3.3 File Size and Type Validation

Restrict file size or type using conditional checks:

@app.post("/validate-file")
async def validate_file(file: UploadFile = File(...)):
    # Limit file size to 1MB (1024*1024 bytes)
    if file.size > 1024 * 1024:
        return JSONResponse(status_code=400, content={"message": "File size exceeds 1MB"})

    # Allow only image types
    if file.content_type not in ["image/jpeg", "image/png"]:
        return JSONResponse(status_code=400, content={"message": "Only JPG/PNG formats are supported"})

    return {"message": "File validation passed"}

3.4 Large File Streaming

For large files (e.g., videos, compressed files), stream to avoid memory overflow:

@app.post("/large-file")
async def large_file(file: UploadFile = File(...)):
    try:
        # Stream writing (asynchronous reading to avoid memory issues)
        with open(f"uploads/large_{file.filename}", "wb") as f:
            async for chunk in file.file:  # Read in chunks
                f.write(chunk)
        return {"message": "Large file uploaded successfully"}
    finally:
        await file.close()

4. Practical Tips and Best Practices

4.1 File Path Management

Use a fixed directory (e.g., uploads/) and ensure it exists:

import os

def ensure_dir(directory):
    if not os.path.exists(directory):
        os.makedirs(directory)

ensure_dir("uploads")  # Create directory on startup

4.2 Custom File Renaming

Avoid filename conflicts by generating unique names:

import uuid

def get_unique_filename(original_name):
    # Preserve file extension and generate a unique name
    name, ext = os.path.splitext(original_name)
    unique_name = f"{uuid.uuid4()}{ext}"
    return unique_name

# Usage example:
filename = get_unique_filename(file.filename)
with open(f"uploads/{filename}", "wb") as f:
    f.write(await file.read())

4.3 Error Handling

Catch exceptions to improve robustness:

@app.post("/safe-upload")
async def safe_upload(file: UploadFile = File(...)):
    try:
        if file.size > 5 * 1024 * 1024:  # Limit to 5MB
            raise ValueError("File too large")
        # Process file...
        return {"status": "success"}
    except ValueError as e:
        return JSONResponse(status_code=400, content={"error": str(e)})
    except Exception as e:
        return JSONResponse(status_code=500, content={"error": "Server error"})

5. Summary

Through this article, you have mastered the core skills of FastAPI file uploads:
- Basic: Single file upload and metadata retrieval using UploadFile.
- Advanced: Multiple file uploads, mixed form data with files, and streaming large files.
- Practical Tips: File validation, path management, and error handling.

FastAPI’s file upload functionality is both simple and powerful. Combined with the auto-generated API documentation (/docs), you can quickly build a stable and reliable file upload service.

Note: In production environments, use professional storage services (e.g., MinIO, S3) instead of local storage to avoid excessive server disk pressure.

Xiaoye