I am working on a Vue application with a Laravel back-end API. After clicking on a link I would like to do a call to the server to download a certain file (most of the time a PDF file). When I do a get request with axios I get a PDF in return, in the body of the response. I would like to download that file directly.
To give you a better view of how the response is looking like:
(note: I know a real text response is better than an image but I don't see any way to return that because of the length of the actual PDF content..)
Is there any way of downloading that file with JavaScript or something? It has to be specific a direct download without clicking on the button again.
Code
// This method gets called when clicking on a link
downloadFile(id) {
const specificationId = this.$route.params.specificationId;
axios
.get(`${this.$API_URL}/api/v1/suppliersmanagement/product-specifications/${specificationId}/fileupload/${id}/download`, {
headers: this.headers,
})
.then(response => {
console.log(response);
// Direct download the file here..
})
.catch(error => console.log(error));
},
As #Sandip Nirmal suggested I've used downloadjs and that worked out pretty good! Had to make a few adjustments to my code but in the end it worked out.
My new code
// npm i downloadjs
import download from 'downloadjs'
// method
downloadFile(file) {
const specificationId = this.$route.params.specificationId;
axios
.get(`${this.$API_URL}/api/v1/suppliersmanagement/product-specifications/${specificationId}/fileupload/${file.id}/download`, {
headers: this.headers,
responseType: 'blob', // had to add this one here
})
.then(response => {
const content = response.headers['content-type'];
download(response.data, file.file_name, content)
})
.catch(error => console.log(error));
},
You should use 'responseType' option. For example:
axios.get(
url,
{responseType: 'blob'} // !!!
).then((response) => {
window.open(URL.createObjectURL(response.data));
})
You have 2 options for this. If you want to do it from server and if you are using Node.js as a backend. You can do it easily using res.download method of express. You can follow this answer for that Download a file from NodeJS Server using Express.
But if you want to handle it from client then there are few options since you can't use axios, XHR, fetch to download file directly. You can either use download.js or write your own code in following way.
return axios({
url: '/download', // download url
method: 'get',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
mode: 'no-cors'
}
})
.then(response => response.blob())
.then(blob => {
var url = window.URL.createObjectURL(blob)
var a = document.createElement('a')
a.href = url
a.download = fileName
a.click()
a.remove()
setTimeout(() => window.URL.revokeObjectURL(url), 100)
})
Since response returned from server is in json format you need to convert it into ObjectURL and set it to anchor tag.
If you sneak inside download.js code you will find same implementation.
2022 answer: using node.js, fs.promises and async/await
The key is using responseType: 'stream' per the Axios docs.
import axios from 'axios';
import { writeFile } from 'fs/promises';
const downloadFile = async () => {
const response = await axios.get('https://someurl', {
params: {
// ...
},
// See https://axios-http.com/docs/api_intro
responseType: 'stream',
});
const pdfContents = response.data;
await writeFile('file.pdf', pdfContents);
};
You can do it like this
download(filename) {
fetch(url , { headers })
.then(response => response.blob())
.then(blob => URL.createObjectURL(blob))
.then(uril => {
var link = document.createElement("a");
link.href = uril;
link.download = filename + ".csv";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
});
}
here I want to download a CSV file, So I add .csv to the filename.
const downloadPDF = (id, fileName) => {
axios({
method: 'get',
url: `https://api.example.com/pdf/invoice/${id}`,
headers: {
'Authorization': 'Bearer ' + localStorage.getItem('token'),
'Content-Type': 'application/json'
},
responseType: 'blob'
}).then(function (response) {
const a = document.createElement('a');
a.href = window.URL.createObjectURL(response.data);
a.download = `${fileName}.pdf`;
document.body.appendChild(a);
a.click();
a.remove();
});
}
Related
I am receiving a ReadableStream from a server, returned from my fetch call.
A ReadableStream is returned but I don't know how to trigger a download from this stage. I can't use the url in an href because it requires an Authorization token.
I don't want to install fs on the client so what options do I have?
try {
const res = await fetch(url, {
method: 'GET',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/octet-stream'
}
});
const blob = await res.blob();
const newBlob = new Blob([blob]);
const newUrl = window.URL.createObjectURL(newBlob);
const link = document.createElement('a');
link.href = newUrl;
link.setAttribute('download', 'filename');
document.body.appendChild(link);
link.click();
link.parentNode.removeChild(link);
window.URL.revokeObjectURL(newBlob);
} catch (error) {
console.log(error);
}
Update 1
I converted the file to a Blob, then passed it into a newly generated href. Successfully downloaded a file. The end result was the ReadStream contents as a .txt file.
Meaning stuff like this
x:ÚêÒÓ%¶âÜTb∞\܃
I have found 2 solutions, both worked but I was missing a simple addition to make them work.
The native solution is
try {
const res = await fetch(url, {
method: 'GET',
headers: {
Authorization: `Bearer ${token}`
}
});
const blob = await res.blob();
const newBlob = new Blob([blob]);
const blobUrl = window.URL.createObjectURL(newBlob);
const link = document.createElement('a');
link.href = blobUrl;
link.setAttribute('download', `${filename}.${extension}`);
document.body.appendChild(link);
link.click();
link.parentNode.removeChild(link);
// clean up Url
window.URL.revokeObjectURL(blobUrl);
This version is using the npm package steamSaver for anyone who would prefer it.
try {
const res = await fetch(url, {
method: 'GET',
headers: {
Authorization: `Bearer ${token}`
}
});
const fileStream = streamSaver.createWriteStream(`${filename}.${extension}`);
const writer = fileStream.getWriter();
const reader = res.body.getReader();
const pump = () => reader.read()
.then(({ value, done }) => {
if (done) writer.close();
else {
writer.write(value);
return writer.ready.then(pump);
}
});
await pump()
.then(() => console.log('Closed the stream, Done writing'))
.catch(err => console.log(err));
The key for why it was not working was because I did not include the extension, so it either errored out because of the mimetype was wrong or it opens a .txt file with a string of the body instead of the image.
I am downloading a pdf file from API, but I am getting a blank PDF. I have tested the API endpoint and able to get the byte stream on the console and when I save it to File, it got saved and the file looks good. Getting the same response back to the front end using React and I could see the PDF byte stream in the response.
However, I could not see the content. It says the file is damaged or corrupted when I opened the downloaded PDF from my local.
I have looked at many examples and are following the same pattern, but I think I am missing something here.
My API Java endpoint definition looks like below
#GetMapping(value = "/fetchFile")
public ResponseEntity<byte[]> fetchFile(#RequestParam final String key) {
FileResponse response = myService.readFile(key);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + key.substring(key.lastIndexOf('/') + 1) + "\"");
return Mono.just(ResponseEntity.ok().headers(httpHeaders).contentLength(response.getContentLength())
.contentType(MediaType.parseMediaType(response.getContentType()))
.body(response.getResponseBytes()));
}
Frontend:
rounterFetchFile.js
router.get('/', (request, resp) => {
axios({
method: 'get',
baseURL: 'http://mybackend.apibase.url',
responseType: 'blob',
url: '/fetchFile',
params: {
fileKey: 'myfile.pdf'
}
})
.then(response => {
return resp.send(response.data)
})
.catch(error => {
console.error(error)
return resp.status(error.response.status).end()
})
})
in myFileComoponent.js
//a function that reads the response from rounterFetchFile.js
const getDocumentOnClick = async () => {
try {
var {data} = await pullMyPDF()
var blob = new Blob([data], { type: "application/pdf" });
var link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = "myFileName.pdf";
link.click();
} catch (e) {
console.log(e)
}
}
Here
var {data} = await pullMyPDF()
is returning the following content. I compared it with the result returned by the Postman, and it is the same. The generated file size is not empty from the react too. I am not able to find out where is it wrong
Below is the response from API endpoint for the fetchFile
I had a similar problem and I fixed it with this:
spa
axios.post(
'api-url',
formData,
{
responseType: 'blob',
headers: {
'Accept': 'application/pdf'
}
})
.then( response => {
const url = URL.createObjectURL(response.data);
this.setState({
filePath: url,
fileType: 'pdf',
})
})
.catch(function (error) {
console.log(error);
});
api
[HttpPost]
public async Task<IActionResult> Post()
{
var request = HttpContext.Request;
var pdfByteArray = await convertToPdfService.ConvertWordStreamToPdfByteArray(request.Form.Files[0], "application/msword");
return File(pdfByteArray, "application/pdf");
}
When the response type is a blob and accepted 'application / pdf' in the header, with that config the job is done ;) ...
Something that worked for me was to send the bytes as base64 from the controller.
API:
public async Task<ActionResult> GetAsync() {
var documentBytes = await GetDocumentAsync().ConfigureAwait(false);
return Ok(Convert.ToBase64String(documentBytes))
}
Front End:
client.get(url, {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
})
.then(response => {
const link = document.createElement('a');
link.href = "data:application/octet-stream;base64," + response.data;
link.download = 'file.pdf';
link.click();
})
.catch(error => {
console.log(error);
})
I hope this solves your problem.
I have a sample.tar.gz report that's getting generated on my remote system and needs to be downloaded on a button click. I am sending a request via the server and needs to be added to file sample.tar.gz on local system.
On the server side code, I do see binary data in the response
body:"�q'l_�=ks�6���_��^��*q���d��i��bIs9���-H��ṙl����5�_�P4���e�#h��F���}�y���,I�L]/~Bj�,~�USU%E�\r�I�UM�F��R��4C� |��q�\tn[�4���_�^z"�k�u���*�/������G�,��iC믚���%�8Y\t��‚\r���E!YP���U�r/#�d0<[��W=ũ��5t���9�&�X��`cF�5��AJ%[T�I�h�D������L6���[\nC���-5�Fy�9��L��IWy#9hv\+�E%�#N���r�٦��s�5"�P*ğa�����h\̆(��&!Þ!x9a�~�"5���('��>�Iwڂ�g��"�..."
But when the below code runs, I am able to download the file:
export const SystemReport = () => {
downloadReport = (url) => {
const options = {
credentials: 'same-origin',
responseType: 'arraybuffer',
method: 'GET',
headers: {
'Accept': 'application/gzip; charset=utf-8',
'Content-Type': 'application/json; charset=utf-8'
}
}
fetch(url, options)
.then(res => {return res.blob()})
.then(blob => {
let url = window.URL.createObjectURL(blob, {type: 'application/zip'})
let a = document.createElement('a')
a.href = url
a.download = 'test.tar.gz'
a.click()
}).catch(err => console.error(err))
}
const fileUrl = `<System-Path>/api/system-report/download/${<file-name>}`
return (
<div className='download-report'>
<a href='#' onClick={() => downloadReport(fileUrl)} target='_blank' download>
<Download16 />
</a>
</div>
)
}
But when I try to unzip the file, I get the below error
# tar -xvzf ~/Downloads/sample.tar.gz
tar: Error opening archive: Unrecognized archive format
Tried looking for some solutions online since I am still learning about these things, but no luck yet. Any help will be appreciated!
Edit: There is a return, but I somehow forgot to add it here. Have edited the code.
You have made a mistake in the following line:
.then(res => {res.blob()})
The function res => {res.blob()} is equivalent (if we don't take the differences between arrow and regular functions into account) to:
function (res) {
res.blob();
}
It has no return value, so blob in the next .then block will be equal to undefined.
You need to use the res => res.blob() syntax which is equivalent to the following code:
function (res) {
return res.blob();
}
None of the other solutions I have read on SO have worked so far, so please bear with me.
I have a frontend in React, where the user sends a request for a file download, along with some variables, to the backend:
const data = text.value;
fetch("http://localhost:4000/dl", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
data,
file: filetypes.selected,
}),
}).then((res) => {
...???...
});
the data variable is some JSON, like so: {"arrowParens": "always"}.
the file variable contains the filename to be downloaded: .prettierrc.
My backend is in NodeJs/express, and handles the request like so:
index.ts:
app.use("/dl", getFile);
getFile.ts:
import appRoot from "app-root-path";
const index = async (req: Request, res: Response) => {
const { data, file } = req.body;
// download file if folder exists
if (fs.existsSync(`${appRoot}/tmp/${file}`)) {
res.sendFile(
".prettierrc",
{
dotfiles: "allow",
headers: {
"Content-Type": "json",
},
root: path.join(__dirname, "../tmp"),
}
);
}
};
I'm getting the correct response back from the server: POST /dl 200 11.878 ms - 55 but the file isn't downloading, so I think I have to have some extra code. I've read another post where OP created an anchor tag, and set the href to the blob url, but this doesn't seem to work since the response doesn't show me a URL. I had to opt for the POST request since I have to send those file-determining variables. Not sure if method makes a difference here.
How do I get the browser to prompt the download of my .prettierrc file? What am I missing?
You have to create an anchor tag with href and trigger the click.
something like this
fetch("http://localhost:4000/dl", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
data,
file: filetypes.selected,
}),
}).then(resp => resp.blob())
.then(blob => {
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.href = url;
a.download = id;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
})
I am receiving a pdf file as api call response from a node.js backend.The file opens in browser window in an encoded format.I have tried to download but the downloaded file has error opening it (error: failed to load pdf document).I am told the response body is base64 encoded.
Is their any way the pdf can be open /downloaded correctly.I am using react.js and is new to it.
code snippet :
import FileDownload from 'js-file-download';
export function getTaxInvoice({token}){
const authString = `Bearer ${token}`;
return (dispatch) => {
return axios.get(`${MAIN_URL}/rental_invoice`,{
headers: {Authorization: authString, 'Accept': 'application/json','Content-Type': 'application/pdf'},
responseType: "arraybuffer",//I have tried with blob as well
encoding: null
})
.then((response)=>{
FileDownload(response, 'some.pdf');
const taxInvoiceUrl = window.URL.createObjectURL(new Blob([response.data]));
window.open(taxInvoiceUrl, "_blank");
console.log( response);
// dispatch(taxInvoiceLoadSuccess(taxInvoiceUrl));
// dispatch(onViewChanged("rental_invoice"));
})
.catch((error)=>{
dispatch(taxInvoiceLoadFailed());
})
}
}
response from api call:image snippet
Here is an example of some code I have used in the past to do this:
function downloadURI (url, name) {
var link = document.createElement('a')
link.download = name
link.href = url
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}
export function download (url, type = 'application/pdf', name = 'example') {
get(url, (err, result) => {
if (err) return handleError(err)
const blob = new Blob([result.body], { type })
const downloadUrl = URL.createObjectURL(blob)
downloadURI(downloadUrl, name)
})
}
It will download the file and create an object url and automatically trigger opening the file by programatically clicking a link.
Finally solved the issue.(My senior dev helped me).Final code is below:
install base64js and filedownload on npm .
export function getTaxInvoice({token}){
const authString = `Bearer ${token}`;
return (dispatch) => {
return axios.get(`${MAIN_URL}/rental_invoice`,{
headers: {Authorization: authString, 'Accept': 'application/pdf','Content-Type': 'application/pdf'}
})
.then((response)=>{
FileDownload(base64js.toByteArray(response.data), 'some.pdf');
const taxInvoiceUrl = window.URL.createObjectURL(new Blob([base64js.toByteArray(response.data)], { type: "application/pdf" }) );
window.open(taxInvoiceUrl, "_blank");
dispatch(taxInvoiceLoadSuccess(response.data));
dispatch(onViewChanged("rental_invoice"));
})
.catch((error)=>{
console.log(error);
dispatch(taxInvoiceLoadFailed());
})
}
}
Try changing
FileDownload(response, 'some.pdf');
to
FileDownload(response.data, 'some.pdf');