How to send a base64 encoded image to a FastAPI backend?

As described in this answer, uploaded files are sent as form data. As per FastAPI documentation:

Data from forms is normally encoded using the “media type”
application/x-www-form-urlencoded when it doesn’t include files.

But when the form includes files, it is encoded as
multipart/form-data. If you use File, FastAPI will know it has to get
the files from the correct part of the body.

Regardless of what type you used, either bytes or UploadFile, since…

If you declare the type of your path operation function parameter as
bytes, FastAPI will read the file for you and you will receive the
contents as bytes.

Hence, the 422 Unprocessable entity error. In your example, you send binary data (using application/octet-stream for content-type), however, your API’s endpoint expects form data (i.e., multipart/form-data).

Option 1

Instead of sending a base64 encoded image, upload the file as is, either using an HTML form, as shown here, or Javascript, as shown below. As others pointed out, it is imperative that you set the contentType option to false, when using JQuery. Using Fetch API, as shown below, it is best to leave it out as well, and force the browser to set it (along with the mandatory multipart boundary). For async read/write in the FastAPI backend, please have a look at this answer.

app.py:

import uvicorn
from fastapi import File, UploadFile, Request, FastAPI
from fastapi.templating import Jinja2Templates

app = FastAPI()
templates = Jinja2Templates(directory="templates")

@app.post("/upload")
def upload(file: UploadFile = File(...)):
    try:
        contents = file.file.read()
        with open("uploaded_" + file.filename, "wb") as f:
            f.write(contents)
    except Exception:
        return {"message": "There was an error uploading the file"}
    finally:
        file.file.close()
        
    return {"message": f"Successfuly uploaded {file.filename}"}

@app.get("/")
def main(request: Request):
    return templates.TemplateResponse("index.html", {"request": request})

templates/index.html

<script>
function uploadFile(){
    var file = document.getElementById('fileInput').files[0];
    if(file){
        var formData = new FormData();
        formData.append('file', file);
        fetch('/upload', {
               method: 'POST',
               body: formData,
             })
             .then(response => {
               console.log(response);
             })
             .catch(error => {
               console.error(error);
             });
    }
}
</script>
<input type="file" id="fileInput" name="file"><br>
<input type="button" value="Upload File" onclick="uploadFile()">

If you would like to use Axioslibrary for the upload, please have a look at this answer.

Option 2

If you still need to upload a base64 encoded image, you can send the data as form data, using application/x-www-form-urlencoded as the content-type; while in your API’s endpoint, you can define a Form field to receive the data. Below is a complete working example, where a base64 encoded image is sent, received by the server, decoded and saved to the disk. For base64 encoding, the readAsDataURL method is used on client side. Please note, the file writing to the disk is done using synchronous writing. In scenarios where multiple (or large) files need to be saved, it would be best to use async writing, as described here.

app.py

from fastapi import Form, Request, FastAPI
from fastapi.templating import Jinja2Templates
import base64

app = FastAPI()
templates = Jinja2Templates(directory="templates")

@app.post("/upload")
def upload(filename: str = Form(...), filedata: str = Form(...)):
    image_as_bytes = str.encode(filedata)  # convert string to bytes
    img_recovered = base64.b64decode(image_as_bytes)  # decode base64string
    with open("uploaded_" + filename, "wb") as f:
        f.write(img_recovered)
    return {"message": f"Successfuly uploaded {filename}"}

@app.get("/")
def main(request: Request):
    return templates.TemplateResponse("index.html", {"request": request})

templates/index.html

<script type="text/javascript">
    function previewFile() {
        const preview = document.querySelector('img');
        const file = document.querySelector('input[type=file]').files[0];
        const reader = new FileReader();
        reader.addEventListener("load", function () { 
            preview.src = reader.result; //show image in <img tag>
            base64String = reader.result.replace("data:", "").replace(/^.+,/, "");
            uploadFile(file.name, base64String)
        }, false);

        if (file) {
            reader.readAsDataURL(file);
        }
    }
    function uploadFile(filename, filedata){
        var formData = new FormData();
        formData.append("filename", filename);
        formData.append("filedata", filedata);
         fetch('/upload', {
               method: 'POST',
               body: formData,
             })
             .then(response => {
               console.log(response);
             })
             .catch(error => {
               console.error(error);
             });
      }

</script>
<input type="file" onchange="previewFile()"><br>
<img src="" height="200" alt="Image preview...">

Leave a Comment