FastAPI - Uploading multiple files using Axios raises Bad Request error
Asked Answered
G

2

3

Client code:

!<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title></title>
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</head>

<form id="uploadForm" role="form" method="post" enctype=multipart/form-data>
    <input type="file" id="file" name="file" multiple>
    <input type=button value=Upload onclick="uploadFile()">
</form>

<script type="text/javascript">
function uploadFile() {
   var formData = new FormData();
    var imagefile = document.querySelector('#file');
    formData.append("images", imagefile.files);
    axios.post('http://127.0.0.1:8000/upload', formData, {
        headers: {
          'Content-Type': 'multipart/form-data'
        }
    })
}
</script>
</body>
</html>

Server code:

from fastapi import FastAPI, File, UploadFile, FastAPI
from typing import Optional, List
from fastapi.responses import FileResponse, HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.middleware.cors import CORSMiddleware

...

def save_file(filename, data):
    with open(filename, 'wb') as f:
        f.write(data)
        print('file saved')

@app.post("/upload")
async def upload(files: List[UploadFile] = File(...)):
    print(files)
    for file in files:
        contents = await file.read()
        save_file(file.filename, contents)
        print('file received')

    return {"Uploaded Filenames": [file.filename for file in files]}

I get the following error:

 ←[32mINFO←[0m:     127.0.0.1:10406 - "←[1mPOST /upload HTTP/1.1←[0m" ←[31m400 Bad Request←[0m 

I have tried to upload a single file via form action and all works fine, but I need to upload two files.

Gerek answered 27/4, 2022 at 9:53 Comment(1)
You're using images as the form key in JS, but files in your FastAPI definition. Does the 400 error have a body with more details? Does it work properly with a regular form and <input type="file" name="images (or files)" multiple>?Misguided
S
3

First, when uploading files or form data, one should use the same form key defined in their endpoint/route. In your case, that is files. Hence, on client side you should use that key instead of images.

Second, the way to upload multiple files is to loop through the array of files (i.e., imagefile.files in your code) and add each file to the FormData object.

Third, the error seems to occur due to some bug/changes in the 0.27.1 version of Axios. Here is a recent related issue on GitHub. Using the latest version, i.e., 0.27.2, from cdnjs here resolves the issue.

Alternatively, you could use Fetch API, similar to this answer (you can add the list of files in the same way as shown in the Axios example below). In Fetch API, when uploading files to the server, you should not explicitly set the Content-Type header on the request, as explained here.

Working Example (using Axios)

<!DOCTYPE html>
<html>
   <head>
      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <title>Upload Files</title>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.27.2/axios.min.js"></script>   
   </head>
   <body>
      <input type="file" id="fileInput" multiple><br>
      <input type="button" value="Upload" onclick="uploadFile()">
      <script type="text/javascript">
         function uploadFile() {
             var fileInput = document.querySelector('#fileInput'); 
         
             if (fileInput.files[0]) {
                var formData = new FormData();
                for (const file of fileInput.files)
                    formData.append('files', file);

                 axios({
                         method: 'post',
                         url: '/upload',
                         data: formData,
                         headers: {
                             'Accept': 'application/json',
                             'Content-Type': 'multipart/form-data'
                         }
                     })
                    .then(response => {
                        console.log(response);
                    })
                    .catch(error => {
                        console.error(error);
                    });
             }
         }
      </script>
   </body>
</html>
Selfpity answered 27/4, 2022 at 13:7 Comment(0)
K
0

Starting from Axios v 0.27.2 you can do this easily:

axios
    .postForm("https://httpbin.org/post", document.querySelector("#fileInput").files)

All the files will be submitted with files[] key.

More verbose example:

    axios.postForm("https://httpbin.org/post", {
      "myField": "foo"
      "myJson{}": {x:1, y: 'bar'}, 
      "files[]": document.querySelector("#fileInput").files
    })
Kerikeriann answered 28/4, 2022 at 13:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.