When using a React client with an Express API, how can the React client download a file that was send by the Express API?
Problem:
Express server
// Route handler for /api/files/testfile
const getFile = async (req, res, next) => {
// File
const fileName = 'file.csv';
const filePath = path.join(__dirname, '/../../public/', fileName);
// File options
const options = {
headers: {
'x-timestamp': Date.now(),
'x-sent': true,
'content-disposition': "attachment; filename=" + fileName, // gets ignored
'content-type': "text/csv"
}
}
try {
res.download(
filePath,
fileName,
options
);
console.log("File sent successfully!");
}
catch (error) {
console.error("File could not be sent!");
next(error);
}
});
React client
// When the user clicks the "Download as CSV" button
handleDownloadFile = () => {
axios
.get(
`/api/files/testfile`, {
responseType: 'blob',
headers: {
'Content-Type': 'text/csv',
}
}
)
.then(response => {
console.log(response.headers); // does not include content-disposition
console.log("File downloading successfully!");
})
.catch( (error) => {
console.error("File could not be downloaded:", error);
});
}
I read that this might have to do with the content-disposition header. I tried setting in (see my above code) but the header does not get send to the client.
Undesirable "solutions":
In the React app: Create a new a element, set its href attribute and trigger a click via JavaScript. I am looking for a solution that does not require this JS hack.
In the React app: Use a with target="_blank" instead of Axios. However, that is not suitable for me, as it would bypass my axios config settings (API url, auth token, etc)
It seems like you have to tell axios where the file is directly based on this example:
axios({
url: 'http://localhost:5000/static/example.pdf',
method: 'GET',
responseType: 'blob', // important
}).then((response) => {
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', 'file.pdf');
document.body.appendChild(link);
link.click();
});
I would assume you can simply change the response on your api to return the blob using a new Blob for the file. But the main part of what it seems to require is the .then response on your axios get call. This way you can still authenticate users' status with jwt and protect your files appropriately.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With