Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 40

Uploading Files with FastAPI

Recommended resources

Hello! Welcome to the next lesson in our FastAPI course.

In the last session, we fortified our API by implementing specific error handling for the model inference step. You learned how to use both HTTPException and custom exception handlers to gracefully manage internal errors, ensuring your API remains stable and provides informative responses even when things go wrong internally.

Now, we're diving into Module 9, "Advanced I/O," to handle a different kind of input: files. Many real-world AI applications, especially in computer vision, don't just process JSON data; they process images, audio, or other binary files.

Today's learning outcome is to create an endpoint that accepts an image upload using UploadFile. This is the first and most fundamental step towards building an API that can, for example, take a user's image and run it through a classification or object detection model.

We will cover:

  1. The necessary setup for handling file uploads.
  2. The difference between reading a file into memory versus using FastAPI's UploadFile.
  3. How to define an endpoint that accepts a file.
  4. How to read the file's content asynchronously.
  5. How to test your new endpoint using the interactive Swagger UI.

Let's get started.

1. Preparing for File Uploads

When a web client sends data to an API, it specifies the Content-Type in the HTTP headers. So far, we've implicitly worked with application/json. However, when a request includes files, the browser encodes the body using multipart/form-data.

FastAPI handles this seamlessly, but it relies on an external library to parse this specific data format.

First, you need to install python-multipart:

pip install python-multipart

This is a one-time setup step for any FastAPI project that needs to handle file uploads.

Request Files

The official FastAPI documentation, 'Request Files', explains this prerequisite and introduces the core concepts. It's the best place to start.

Please read the introduction to understand the need for python-multipart. Then, read the section 'File Parameters with UploadFile' to learn why UploadFile is the preferred method. Pay close attention to the advantages it lists over simply using bytes.

As the documentation highlights, using UploadFile is crucial for performance and memory management. It "spools" the file, meaning it holds small files in memory but automatically saves larger files to disk. This prevents your server's memory from being exhausted if a user uploads a very large image or video.


2. Creating a Basic File Upload Endpoint

Let's build our first file upload endpoint. The process is very similar to creating other endpoints; the main difference is the type hint in the function signature.

You'll need to import UploadFile and File from fastapi.

Create a new POST endpoint. In the function parameter, you'll declare a variable (e.g., file) with the type hint UploadFile. FastAPI uses this hint to understand that it should expect a file from a form field with the name "file".

Here is a minimal example. You can add this to your main.py:

from fastapi import FastAPI, File, UploadFile

app = FastAPI()

@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile):
    """
    Accepts a file upload and returns its original filename.
    """
    return {"filename": file.filename, "content_type": file.content_type}

This endpoint doesn't do much yet—it just receives the file and immediately returns its original filename and MIME type as a JSON response. This is a great way to confirm that the upload mechanism is working correctly.


3. Reading File Contents Asynchronously

The UploadFile object gives you more than just metadata. It provides a file-like interface with async methods to read the contents. Because you've already mastered Python's async/await, this will be a familiar pattern.

To read the entire file into a bytes object in memory, you can use await file.read().

Let's enhance our endpoint to read the file's content and return its size.

from fastapi import FastAPI, File, UploadFile

app = FastAPI()




# Your other endpoints...

@app.post("/upload-image/")
async def upload_image(file: UploadFile):
    """
    Accepts an image upload, reads its contents, and returns metadata.
    """



    # Read the content of the file
    contents = await file.read()
    
    return {
        "filename": file.filename,
        "content_type": file.content_type,
        "size_bytes": len(contents)
    }

The await file.read() line is the key here. It asynchronously reads the file stream (whether from memory or disk) and puts the result into the contents variable. This is a crucial step before you can perform any processing, like feeding the image data to a machine learning model.

The following video provides a concise demonstration of this entire process in the context of an ML application.

Creating APIs For Machine Learning Models with FastAPI

The video 'Creating APIs For Machine Learning Models with FastAPI' by NeuralNine shows this exact pattern. It demonstrates how to define the endpoint and read the file.

Watch the segment from 07:26 to 08:44. Notice how an async function is defined with a parameter file: UploadFile, and how await file.read() is used to get the contents.


4. Testing Your Endpoint with Swagger UI

Now for the hands-on part. With your Uvicorn server running, navigate to http://127.0.0.1:8000/docs. You'll see your new /upload-image/ endpoint listed.

  1. Expand the endpoint details for POST /upload-image/.
  2. Click the "Try it out" button.
  3. You will see a "Request body" section with a field for file.
  4. Click the "Choose File" button and select any image file (e.g., a .jpg or .png) from your computer.
  5. Click "Execute".

You should see a successful 200 response with a JSON body containing the filename, content type, and size of the file you just uploaded.

FastAPI Swagger UI for File Upload Endpoint
This is what the file upload interface looks like in the interactive documentation. The `UploadFile` type hint automatically generates this user-friendly widget for testing.

5. A Practical Example: Saving the Uploaded File

To make this more concrete, let's create a simple endpoint that saves the uploaded file to your server's disk. This is a common pattern for handling user-uploaded assets like profile pictures.

First, create a directory named uploads in the root of your project.

Now, let's write the endpoint. We'll read the file's content and then write it to a new file in our uploads directory.

FastAPI Tutorial #6 - Handling Form Data and File Uploads in FastAPI [2024]

The video 'FastAPI Tutorial #6 - Handling Form Data and File Uploads' from Tisfoulla Academy demonstrates a very clean way to save an uploaded file.

Watch from 01:55 to 02:39. This short clip shows exactly how to open a file in write-binary mode (wb) and save the content you've received.

Here's the code implementing that pattern. You can add this as another endpoint or modify the existing one.

import shutil # Add this import at the top of your file

@app.post("/save-file/")
async def save_upload_file(file: UploadFile):
    """
    Accepts a file and saves it to the local 'uploads' directory.
    """



    # Define the path where the file will be saved
    upload_dir = "uploads"
    file_path = f"{upload_dir}/{file.filename}"
    



    # Save the file
    with open(file_path, "wb") as buffer:
        shutil.copyfileobj(file.file, buffer)
        
    return {"filename": file.filename, "saved_at": file_path}

Why shutil.copyfileobj?

While contents = await file.read() is simple, it reads the entire file into memory first. The code above uses a more memory-efficient approach. file.file gives you direct access to the underlying spooled file object. shutil.copyfileobj copies data from this source to the destination file in chunks, preventing high memory usage for large files.

Run this code and test the /save-file/ endpoint via the docs. After executing, check your uploads directory—you'll find the image you uploaded!


Conclusion

Great work! You have successfully created an endpoint that accepts image uploads, a cornerstone for building many types of AI-powered APIs.

Key Takeaways:

  • File uploads require the python-multipart library.
  • Use the UploadFile type hint in your endpoint function to accept files. It's memory-efficient and designed for this purpose.
  • In an async endpoint, use await file.read() to get the entire file content as bytes.
  • For saving files, shutil.copyfileobj(file.file, ...) is a memory-efficient alternative to reading the whole file into memory first.
  • The Swagger UI provides an excellent, out-of-the-box interface for testing your file upload endpoints.

In our next lesson, we'll build on this foundation. An endpoint that blindly accepts any file can be a security risk. We will learn how to perform basic validation on an uploaded file, such as checking its content type, to ensure our API only processes the kinds of files it expects.