Search code examples
javascriptpythonfastapifetch-api

FastAPI returns "Error 422: Unprocessable entity" when I send multipart form data with JavaScript Fetch API


I have some issue with using Fetch API JavaScript method when sending some simple formData like so:

function register() {
  var formData = new FormData();
  var textInputName = document.getElementById('textInputName');
  var sexButtonActive = document.querySelector('#buttonsMW > .btn.active');
  var imagesInput = document.getElementById('imagesInput');

  formData.append('name', textInputName.value);
  if (sexButtonActive != null){
    formData.append('sex', sexButtonActive.html())
  } else {
    formData.append('sex', "");
  }
  formData.append('images', imagesInput.files[0]);

  fetch('/user/register', {
    method: 'POST',
    data: formData,
  })
  .then(response => response.json());
}
document.querySelector("form").addEventListener("submit", register);

And on the server side (FastAPI):

@app.post("/user/register", status_code=201)
def register_user(name: str = Form(...), sex: str = Form(...), images: List[UploadFile] = Form(...)):
try:
    print(name)
    print(sex)
    print(images)
    return "OK"
except Exception as err:
    print(err)
    print(traceback.format_exc())
    return "Error"

After clicking on the submit button I get Error 422: Unprocessable entity. So, if I'm trying to add header Content-Type: multipart/form-data, it also doesn't help cause I get another Error 400: Bad Request. I want to understand what I am doing wrong, and how to process formData without such errors?


Solution

  • The 422 error response body will contain an error message about which field(s) is missing or doesn’t match the expected format. Since you haven't provided that (please do so), my guess is that the error is triggered due to how you defined the images parameter in your endpoint. Since images is expected to be a List of File(s), you should instead define it using the File type instead of Form. For example:

    images: List[UploadFile] = File(...)
                               ^^^^    
    

    When using UploadFile, you don't have to use File() in the default value of the parameter, meaning that the below should work as well:

    images: List[UploadFile]
    

    Hence, the FastAPI endpoint should look similar to this:

    @app.post("/user/register")
    async def register_user(name: str = Form(...), images: List[UploadFile] = File(...)):
        pass
    

    In the frontend, make sure to use the body (not data) parameter in the fetch() function to pass the FormData object (see example in MDN Web Docs). For instance:

    var nameInput = document.getElementById('nameInput'); 
    var imagesInput = document.getElementById('imagesInput');
    
    var formData = new FormData();
    formData.append('name', nameInput.value);
    for (const file of imagesInput.files)
        formData.append('images', file);
    
    fetch('/user/register', {
          method: 'POST',
          body: formData,
       })
       .then(response => {
          console.log(response);
       })
       .catch(error => {
          console.error(error);
       });
    

    Please have a look at this answer, as well as this answer, which provide working examples on how to upload multiple files and form data to a FastAPI backend, using Fetch API in the frontend.

    As for manually specifying the Content-Type when sending multipart/form-data, you don't have to (and shouldn't) do that, but rather let the browser set the Content-Type—please take a look at this answer for more details.