6

Frontend = React, backend = FastApi. How can I simply send an image from the frontend, and have the backend saving it to the local disk ? I've tried different ways: in an object, in a base64 string, etc. But I can't manage to deserialize the image in FastApi. It looks like an encoded string, I tried writing it to a file, or decoding it, but with no success.

const [selectedFile, setSelectedFile] = useState(null);
const changeHandler = (event) => {setSelectedFile(event.target.files[0]);   };
const handleSubmit = event => {

const formData2 = new FormData();
formData2.append(
"file",
selectedFile,
selectedFile.name
);

const requestOptions = {
    method: 'POST',
    headers: { 'Content-Type': 'multipart/form-data' },
    body: formData2 // Also tried selectedFile
};
  fetch('http://0.0.0.0:8000/task/upload_image/'+user_id, requestOptions)
    .then(response => response.json())
}

return (  <div
            <form onSubmit={handleSubmit}>
              <fieldset>
                  <label htmlFor="image">upload picture</label><br/>
                  <input name="image" type="file" onChange={changeHandler} accept=".jpeg, .png, .jpg"/>

              </fieldset>
              <br/>
              <Button color="primary" type="submit">Save</Button>
            </form>
</div>
);

And the backend:

@router.post("/upload_image/{user_id}")
async def upload_image(user_id: int, request: Request):
    body = await request.body()
    
    # fails (TypeError)
    with open('/home/backend/test.png', 'wb') as fout:
        fout.writelines(body) 

I also tried to simply mimic the client with something like this: curl -F media=@/home/original.png http://0.0.0.0:8000/task/upload_image/3 but same result...

----- [Solved] Removing user_id for simplicity. The server part must look like this:

@router.post("/uploadfile/")
async def create_upload_file(file: UploadFile = File(...)):
    out_path = 'example/path/file'
    async with aiofiles.open(out_path, 'wb') as out_file:
        content = await file.read()
        await out_file.write(content)

And for some reason, the client part should not include the content-type in the headers:

function TestIt ( ) {
    const [selectedFile, setSelectedFile] = useState(null);
    const [isFilePicked, setIsFilePicked] = useState(false);
  
    const changeHandler = (event) => {
        setSelectedFile(event.target.files[0]);
        setIsFilePicked(true);
    };
    
    const handleSubmit = event => {
      event.preventDefault();
      const formData2 = new FormData();
      formData2.append(
        "file",
        selectedFile,
        selectedFile.name
      );
      
    const requestOptions = {
        method: 'POST',
        //headers: { 'Content-Type': 'multipart/form-data' }, // DO NOT INCLUDE HEADERS
        body: formData2
    };
      fetch('http://0.0.0.0:8000/task/uploadfile/', requestOptions)
        .then(response => response.json())
        .then(function (response) {
          console.log('response')
          console.log(response)
            });
    }
    return (  <div>
        <form onSubmit={handleSubmit}>
          <fieldset>
              <input name="image" type="file" onChange={changeHandler} accept=".jpeg, .png, .jpg"/>
          </fieldset>
          <Button type="submit">Save</Button>
        </form>
    </div>
  );
}
user992157
  • 161
  • 3
  • 8
  • Are you sending the file from the browser to the api, or to react which then forwards the request to the api? Also, that's not the proper way of accessing the file. See the docs on how to do it https://fastapi.tiangolo.com/tutorial/request-files/?h=file – lsabi Mar 12 '21 at 08:01
  • I am using a react Form to send the request to the API. I also tried the UploadFile module in FastApi, but it was not working better. I'm surprised that there is no simple snippet example of JS-Fastapi for this usecase – user992157 Mar 12 '21 at 16:17

1 Answers1

5

Replying from to your comment: yes there is a simple snippet example of JS-Fastapi and it was answered by me not long ago.

The reason you are not able to access the file is fairly simple: the naming of the python parameters must match the keys of the formData object.

Instead, you are accessing the raw request, which does not make sense.

Simply change your python code as follows:

from fastapi import UploadFile, File

@router.post("/upload_image/{user_id}")
async def upload_image(user_id: int, file: UploadFile = File(...)):
    # Your code goes here

which is well detailed in the official docs

https://fastapi.tiangolo.com/tutorial/request-files/?h=file

FYI

The following are the references to my answers (actually checking, I answered multiple questions related to this topic and should be enough to understand the basic problems one may fall into when uploading files)

How to send a file (docx, doc, pdf or json) to fastapi and predict on it without UI (i.e., HTML)?

How can I upload multiple files using JavaScript and FastAPI?

How to upload file using fastapi with vue? I got error unprocessable 422

Uploading an excel file to FastAPI from a React app

lsabi
  • 3,641
  • 1
  • 14
  • 26
  • Thanks, I actually spent some time on it since your comment yesterday. Updating the python function helps indeed, but I'm still not able to have it working. More precisely, it works with curl, but not from React. There is no much details as the FastApi part just raises a 400 Bad Request / There was an error parsing the body. I'll continue digging. – user992157 Mar 12 '21 at 21:53
  • Can you post the entire error? It's not simple to identify the problem. Though, it could be due to cors activated – lsabi Mar 12 '21 at 22:20
  • It is not due to CORS, the rest of my interaction (react-fastapi) works fine. I don't see any error message unfortunately, only "400 Bad Request" on the server side, and it returns "There was an error parsing the body". I'm trying to find how to get more details. – user992157 Mar 12 '21 at 22:43
  • Ok I think I found how to fix it, from another post: https://stackoverflow.com/questions/39383861/postman-throwing-400-bad-request-for-multipart-form-data-image-upload-with-jerse . For some reason, it works if we don't include Content-Type headers. I'll update my post. Thanks for the Python part. I'll edit my question with more details in case it helps someone else – user992157 Mar 13 '21 at 12:43