27

THE SITUATION:

Frontend: Vue. Backend: Laravel.

Inside the web app I need to let the user download certain pdf files:

  • I need Laravel to take the file and return it as a response of an API GET request.
  • Then inside my Vue web app I need to get the file and download it.

THE CODE:

API:

$file = public_path() . "/path/test.pdf";

$headers = [
    'Content-Type' => 'application/pdf',
];
return response()->download($file, 'test.pdf', $headers);

Web app:

downloadFile() {
  this.$http.get(this.apiPath + '/download_pdf')
    .then(response => {
      let blob = new Blob([response.data], { type: 'application/pdf' })
      let link = document.createElement('a')
      link.href = window.URL.createObjectURL(blob)
      link.download = 'test.pdf'
      link.click()
    })
}

OUTCOME:

Using this code I do manage to download a pdf file. The problem is that the pdf is blank.

Somehow the data got corrupted (not a problem of this particular pdf file, I have tried with several pdf files - same outcome)

RESPONSE FROM SERVER:

The response itself from the server is fine:

enter image description here

PDF:

The problem may be with the pdf file. It definitely looks corrupted data. This is an excerpt of how it looks like the response.data:

enter image description here

THE QUESTION:

How can I properly download a pdf file using Laravel for the API and Vue for the web app?

Thanks!

FrancescoMussi
  • 20,760
  • 39
  • 126
  • 178

4 Answers4

49

SOLUTION:

The code above was correct. What was missing was adding the proper responseType as arraybuffer.

I got scared by those ???? inside the response, and that was misleading me. Those question marks were just okay since pdf is a binary data and is meant to be read by a proper reader.

THE ARRAYBUFFER:

And arraybuffer is precisely used to keep binary data.

This is the definition from the mozilla website:

The ArrayBuffer object is used to represent a generic, fixed-length raw binary data buffer. You cannot directly manipulate the contents of an ArrayBuffer; instead, you create one of the typed array objects or a DataView object which represents the buffer in a specific format, and use that to read and write the contents of the buffer.

And the ResponseType string indicates the type of the response. By telling its an arraybuffer, it then treats the data accordingly.

And just by adding the responseType I managed to properly download the pdf file.

THE CODE:

This is corrected Vue code (exactly as before, but with the addition of the responseType):

downloadFile() {
  this.$http.get(this.appApiPath + '/testpdf', {responseType: 'arraybuffer'})
    .then(response => {
      let blob = new Blob([response.data], { type: 'application/pdf' })
      let link = document.createElement('a')
      link.href = window.URL.createObjectURL(blob)
      link.download = 'test.pdf'
      link.click()
    })
}

EDIT:

This is a more complete solution that take into account other browsers behavior:

downloadContract(booking) {
  this.$http.get(this.appApiPath + '/download_contract/' + booking.id, {responseType: 'arraybuffer'})
    .then(response => {
      this.downloadFile(response, 'customFilename')
    }, response => {
      console.warn('error from download_contract')
      console.log(response)
      // Manage errors
      }
    })
},

downloadFile(response, filename) {
  // It is necessary to create a new blob object with mime-type explicitly set
  // otherwise only Chrome works like it should
  var newBlob = new Blob([response.body], {type: 'application/pdf'})

  // IE doesn't allow using a blob object directly as link href
  // instead it is necessary to use msSaveOrOpenBlob
  if (window.navigator && window.navigator.msSaveOrOpenBlob) {
    window.navigator.msSaveOrOpenBlob(newBlob)
    return
  }

  // For other browsers:
  // Create a link pointing to the ObjectURL containing the blob.
  const data = window.URL.createObjectURL(newBlob)
  var link = document.createElement('a')
  link.href = data
  link.download = filename + '.pdf'
  link.click()
  setTimeout(function () {
    // For Firefox it is necessary to delay revoking the ObjectURL
    window.URL.revokeObjectURL(data)
  }, 100)
},
FrancescoMussi
  • 20,760
  • 39
  • 126
  • 178
  • 2
    Thank you so much @FrancescoMussi! This is very helpful. My pdf downloads but wouldn't open or opens with an error that it cannot be opened. When I added the responseType, everything worked! – bdfios Jul 05 '19 at 19:55
  • responseType as arraybuffer! Works <3 – Frexuz Apr 03 '20 at 03:10
  • I have been working on streaming PDF data for like two days now, I am pulling my hair out. I keep getting "undefined" as my PDF data. I can see the darn PDF data in the network, but somehow I can't save it! – dustbuster Aug 05 '20 at 19:48
  • For typescript, in the IE section `const nav = window.navigator as any` and then use it – Alessandro Lodi Sep 06 '22 at 09:12
5

You won't be able to do the download from Laravel to Vue since both are running at different ports I assume.

Even if you try something like this.

public function getDownload()
    {
        //PDF file is stored under project/public/download/info.pdf
        $file= public_path(). "/download/info.pdf";

        $headers = [
              'Content-Type' => 'application/pdf',
           ];

    return response()->download($file, 'filename.pdf', $headers);
    }

It won't help as you are sending headers to the Laravel Port Try using Vue js libraries and try to send that pdf content on the library

Try this Get help from here

Sagar Ahuja
  • 726
  • 6
  • 23
  • It looks pretty similar. I will give it a try using Axios instead of vue-resource. – FrancescoMussi Jun 07 '18 at 08:56
  • Sure do let me know if it worked or not @johnnyfittizio – Sagar Ahuja Jun 07 '18 at 09:42
  • 1
    In the link you provide I found the actual solution. Everything was correct expect the fact that was missing the responseType. Axios or vue-resource doesn't matter. I wrote the corrected code in an answer. I have credited you for pointing in the right direction. Thanks! – FrancescoMussi Jun 07 '18 at 09:45
2

it's works for me.

from laravel backend:

$pdf = PDF::loadView('your_view_name', ['data' => $data]);
return $pdf->output();

from vuejs frontend:

axios({
url: 'http://localhost:8000/api/your-route',
method: 'GET',
responseType: 'blob',
}).then((response) => {
     var fileURL = window.URL.createObjectURL(new Blob([response.data]));
     var fileLink = document.createElement('a');
     fileLink.href = fileURL;
     fileLink.setAttribute('download', 'file.pdf');
     document.body.appendChild(fileLink);
     fileLink.click();

});
1
downloadFile: function () {
            this.$http.post('{{ route('download.download') }}', {
                _token: "{{ csrf_token() }}",
                inputs: this.inputs
            },{responseType: 'arraybuffer'}).then(response => {
                var filename = response.headers.get('content-disposition').split('=')[1].replace(/^\"+|\"+$/g, '')
                var url = window.URL.createObjectURL(new Blob([response.body],{type:response.headers.get('content-type')}))
                var link = document.createElement('a')
                link.href = url
                link.setAttribute('download', filename)
                document.body.appendChild(link)
                link.click()
            });
        },