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

1

5

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?

Impel answered 18/8, 2022 at 22:38 Comment(0)
S
5

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 utilises 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>
Starch answered 19/8, 2022 at 9:11 Comment(2)
This implies that the full file is first loaded in memory and then save to disk, right ? Is there some way to avoid that ?Indophenol
@Indophenol The example above is based on OP's needs, where the file has to be created and served by two different endpoints. Sure you can avoid that, by returning a Response directly. Please have a look here and here, as well as at this, this and this answer.Starch

© 2022 - 2024 — McMap. All rights reserved.