How to Handle File Uploads in FastAPI

FastAPI is a modern, fast (high-performance), web framework for building APIs with Python based on standard Python type hints. One common requirement in many web applications is the ability to handle file uploads. Whether it’s a user uploading a profile picture, a document, or a media file, FastAPI provides a straightforward way to handle such scenarios. In this blog post, we’ll explore the fundamental concepts, usage methods, common practices, and best practices for handling file uploads in FastAPI.

Table of Contents

  1. Fundamental Concepts
  2. Usage Methods
  3. Common Practices
  4. Best Practices
  5. Conclusion
  6. References

Fundamental Concepts

File and UploadFile

In FastAPI, there are two main ways to handle file uploads: using the File type and the UploadFile type.

  • File: It is used to receive file data as bytes. This is suitable for small files where you want to handle the data directly as a byte string.
  • UploadFile: It is a more advanced way to handle file uploads. It represents an uploaded file and provides useful attributes and methods such as filename, content_type, and file (a file-like object that can be used to read the file’s contents). It is recommended for handling larger files as it reads the file in chunks, which is more memory-efficient.

Multipart Form Data

File uploads in web applications typically use the multipart/form-data encoding type. When a client sends a file to the server, the data is divided into multiple parts, each with its own headers and content. FastAPI can automatically parse this multipart/form-data and extract the file data for you.

Usage Methods

Using File

The following is a simple example of using the File type to handle a file upload:

from fastapi import FastAPI, File

app = FastAPI()

@app.post("/upload-file/")
async def upload_file(file: bytes = File()):
    # Here we just return the length of the file data
    return {"file_size": len(file)}

In this example, the file parameter is of type bytes, and FastAPI will automatically read the entire file contents into memory as a byte string.

Using UploadFile

The following is an example of using the UploadFile type:

from fastapi import FastAPI, UploadFile

app = FastAPI()

@app.post("/upload-file-uploadfile/")
async def upload_file_uploadfile(file: UploadFile = File()):
    contents = await file.read()
    # Here we just return the length of the file data
    return {"filename": file.filename, "file_size": len(contents)}

In this example, the file parameter is of type UploadFile. We can access the file’s filename using file.filename and read the file’s contents using await file.read().

Common Practices

Handling Multiple File Uploads

FastAPI allows you to handle multiple file uploads by using a list of File or UploadFile types. Here is an example of handling multiple file uploads using UploadFile:

from fastapi import FastAPI, UploadFile

app = FastAPI()

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

Saving Uploaded Files

Often, you’ll want to save the uploaded files to disk. Here is an example of saving an uploaded file using UploadFile:

import os
from fastapi import FastAPI, UploadFile

app = FastAPI()

UPLOAD_DIR = "uploads"
if not os.path.exists(UPLOAD_DIR):
    os.makedirs(UPLOAD_DIR)

@app.post("/save-file/")
async def save_file(file: UploadFile = File()):
    file_path = os.path.join(UPLOAD_DIR, file.filename)
    with open(file_path, "wb") as f:
        contents = await file.read()
        f.write(contents)
    return {"message": f"File {file.filename} saved successfully"}

Best Practices

File Size Limitation

To prevent users from uploading extremely large files, you can add file size limitations. Here is an example of adding a file size limitation using a custom middleware:

from fastapi import FastAPI, File, UploadFile, Request, HTTPException

app = FastAPI()

MAX_FILE_SIZE = 1024 * 1024  # 1MB

@app.middleware("http")
async def check_file_size(request: Request, call_next):
    content_length = request.headers.get("content-length")
    if content_length and int(content_length) > MAX_FILE_SIZE:
        raise HTTPException(status_code=413, detail="File size exceeds the limit")
    response = await call_next(request)
    return response

@app.post("/upload-limited-file/")
async def upload_limited_file(file: UploadFile = File()):
    contents = await file.read()
    return {"filename": file.filename, "file_size": len(contents)}

File Type Validation

You can also validate the file type to ensure that only certain types of files are allowed to be uploaded. Here is an example of validating the file type based on the file’s content type:

from fastapi import FastAPI, UploadFile, HTTPException

app = FastAPI()

ALLOWED_FILE_TYPES = ["image/jpeg", "image/png"]

@app.post("/upload-validated-file/")
async def upload_validated_file(file: UploadFile = File()):
    if file.content_type not in ALLOWED_FILE_TYPES:
        raise HTTPException(status_code=400, detail="Invalid file type")
    contents = await file.read()
    return {"filename": file.filename, "file_size": len(contents)}

Conclusion

Handling file uploads in FastAPI is relatively straightforward. By using the File and UploadFile types, you can easily receive and process file data. However, when dealing with file uploads, it’s important to consider factors such as file size limitation and file type validation to ensure the security and stability of your application. With the knowledge and examples provided in this blog post, you should be able to handle file uploads efficiently in your FastAPI applications.

References