javascriptreactjsaxiosfastapi

How to download a file using ReactJS with Axios in the frontend and FastAPI in the backend?


I am trying to create a docx file and send it to the frontend client app, so that it can be downloaded to the user's local machine. I am using FastAPI for the backend. I am using python-docx library also to create the Document.

The code below is used to create a docx file and save it to the server.

@app.post("/create_file")
async def create_file(data: Item):
    document = Document()
    document.add_heading("file generated", level=1)
    document.add_paragraph("test")
    document.save('generated_file.docx')
    return {"status":"Done!"}

The below code is then used to send the created docx file as a FileResponse to the client.

@app.get("/generated_file")
async def download_generated_file():
    file_path = "generated_file.docx"
    return FileResponse(file_path, media_type='application/vnd.openxmlformats-officedocument.wordprocessingml.document', filename=file_path)

On Client side (I am using ReactJS):

createFile = async () => {
   const data = {
      start: this.state.start,
      end: this.state.end,
      text: this.state.text,
   };
   await axios.post("http://localhost:8000/create_file", data).then(() => {
      console.log("processing completed!");
   });
};

downloadFile = async () => {
   await axios.get("http://localhost:8000/generated_file").then((res) => {
      const url = URL.createObjectURL(new Blob([res.data]));
      const link = document.createElement("a");
      link.href = url;
      link.setAttribute("download", "generated.txt");
      link.click();
   });
};

The generated.docx file gets downloaded when downloadFile function is called. However, the docx file is always corrupted and doesn't open. I tried using txt file and it works fine. I need to use docx file, so what can I do?


Solution

  • In the Axios GET request, you have to make sure the responseType parameter is set to blob. Once you get the response from the API, you will need to pass the Blob object (i.e., response.data) to the URL.createObjectURL() function. Below is a fully working example on how to create and download a file (Document), using either Axios or Fetch API in the frontend. This answer utilizes methods and code excerpts from this and this answer, as well as the answers here and here. Plesase refer to the above answers for more details on the methods used below. For demo purposes, the below example uses Jinja2Templates, but, in a similar way, you can use the scripts below in your ReactJS app.

    app.py

    from fastapi import FastAPI, Request
    from fastapi.templating import Jinja2Templates
    from fastapi.responses import FileResponse
    from docx import Document
    
    app = FastAPI()
    templates = Jinja2Templates(directory="templates")
    
    
    @app.get('/')
    def main(request: Request):
        return templates.TemplateResponse("index.html", {"request": request})
        
    @app.post("/create")
    def create_file():
        document = Document()
        document.add_heading("file generated", level=1)
        document.add_paragraph("test")
        document.save('generated_file.docx')
        return {"status":"Done!"}
        
    @app.get("/download")
    def download_generated_file():
        file_path = "generated_file.docx"
        return FileResponse(file_path, media_type='application/vnd.openxmlformats-officedocument.wordprocessingml.document', filename=file_path)
    

    Using Axios

    tempaltes/index.htnl

    <!DOCTYPE html>
    <html>
       <head>
          <title>Create and Download a Document</title>
          <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.27.2/axios.min.js"></script>
       </head>
       <body>
          <input type="button" value="Create Document" onclick="createFile()">
          <div id="response"></div><br>
          <input type="button" value="Download Document " onclick="downloadFile()">
          <script>
             function createFile() {
                axios.post('/create', {
                      headers: {
                         'Accept': 'application/json',
                         'Content-Type': 'application/json'
                      }
                   })
                   .then(response => {
                      document.getElementById("response").innerHTML = JSON.stringify(response.data);
                   })
                   .catch(error => {
                      console.error(error);
                   });
             }
             
             function downloadFile() {
                axios.get('/download', {
                      responseType: 'blob'
                   })
                   .then(response => {
                      const disposition = response.headers['content-disposition'];
                      filename = disposition.split(/;(.+)/)[1].split(/=(.+)/)[1];
                      if (filename.toLowerCase().startsWith("utf-8''"))
                         filename = decodeURIComponent(filename.replace("utf-8''", ''));
                      else
                         filename = filename.replace(/['"]/g, '');
                      return response.data;
                   })
                   .then(blob => {
                      var url = window.URL.createObjectURL(blob);
                      var a = document.createElement('a');
                      a.href = url;
                      a.download = filename;
                      document.body.appendChild(a); // append the element to the dom
                      a.click();
                      a.remove(); // afterwards, remove the element  
                   })
                   .catch(error => {
                      console.error(error);
                   });
             }
          </script>
       </body>
    </html>
    

    Using Fetch API

    tempaltes/index.htnl

    <!DOCTYPE html>
    <html>
       <head>
          <title>Create and Download a Document</title>
       </head>
       <body>
          <input type="button" value="Create Document" onclick="createFile()">
          <div id="response"></div><br>
          <input type="button" value="Download Document" onclick="downloadFile()">
          <script>
             function createFile() {
                fetch('/create', {
                      method: 'POST',
                      headers: {
                         'Accept': 'application/json',
                         'Content-Type': 'application/json'
                      }
                   })
                   .then(response => response.text())
                   .then(data => {
                      document.getElementById("response").innerHTML = data;
                   })
                   .catch(error => {
                      console.error(error);
                   });
             }
             
             function downloadFile() {
                fetch('/download')
                   .then(response => {
                      const disposition = response.headers.get('Content-Disposition');
                      filename = disposition.split(/;(.+)/)[1].split(/=(.+)/)[1];
                      if (filename.toLowerCase().startsWith("utf-8''"))
                         filename = decodeURIComponent(filename.replace("utf-8''", ''));
                      else
                         filename = filename.replace(/['"]/g, '');
                      return response.blob();
                   })
                   .then(blob => {
                      var url = window.URL.createObjectURL(blob);
                      var a = document.createElement('a');
                      a.href = url;
                      a.download = filename;
                      document.body.appendChild(a); // append the element to the dom
                      a.click();
                      a.remove(); // afterwards, remove the element
                   })
                   .catch(error => {
                      console.error(error);
                   });
             }
          </script>
       </body>
    </html>