Home > OS >  Laravel 9 and Javascript: how to download a file returned from Storage::download()
Laravel 9 and Javascript: how to download a file returned from Storage::download()

Time:01-01

DISCLAIMER: Before creating this question, I've checked here, here and here, and also checked Laravel docs.

Context

  • Laravel 9 full-stack
  • No JS framework on front-end, which means I'm using vanilla JS
  • The folders on Storage are setted like this:
    • storage
      • app
        • public
          • folder1
            • folder1A
            • folder1B
            • folder1C
            • etc
  • The files stored in each folder1X are .pdf format and I don't know its names.
  • No folders are empty, nor with invalid/corrupted files.

The problem

I have a FileController.php to download files that are inside a folder1X/ directory. The method to download it is as follows:

public function downloadFileFromStorage(Request $request): mixed
{
    $dirpath = $request->dirpath; // dirpath = public/folder1/folder1X. 

    $files = Storage::allFiles($dirpath);

    return response()->download(storage_path('app\\' . $files[0]));
}

(Note: dirpath is sent in a axios request by client and is also fetched from database on a previous request)

My Javascript CLI needs to enable the download of this file. The download is enabled by clicking on a button. The button calls downloadPDF(dirpath) which works as follows:

function downloadPDF(dirpath) {
    axios.post('/download-pdf-file', { dirpath })
        .then(
            success => {
                const url = success.data
                const a = document.createElement('a')
                a.download = 'file.pdf'
                a.href = url
                a.click()
            },
            error => {
                console.log(error.response)
            }
        )
}

But, when I run this function, I get a about:blank#blocked error.

Attempts

  • Changed the a HTML DOM approach to a window.open(url) on client;
  • Changed response() to Storage::download($files[0], 'file-name.pdf'), and with this I also tried using Blob on client as follows:
success => {
    const blob = new Blob([success.data], { type: 'application/pdf' })
    const fileURL = URL.createObjectURL(blob)
    window.openURL(fileURL)
},
  • Also mixed Blob with the a HTML DOM approach;
  • Changed storage_path argument to /app/public/ before concatenating to $files[0].

UPDATE

Following tips from @BenGooding and @cengsemihsahin, I changed files to the following:

JS


// FileDownload is imported on a require() at the code beginning

function downloadPDF(dirpath) {
    axios({
        url: '/download-pdf-file',
        method: 'GET',
        responseType: 'blob',
        options: {
            body: { dirpath }
        }
    }).then(
        success => {
            FileDownload(success.data, 'nota-fiscal.pdf')
        }
    )
}

PHP:


public function downloadFileFromStorage(Request $request): mixed
{
    $dirpath = $request->dirpath; // dirpath = public/folder1/folder1X. 

    $files = Storage::allFiles($dirpath);
    return Storage::download($files[0], 'filename.pdf');
}

and now it downloads a corrupted PDF that can't be opened.

CodePudding user response:

Finally found the issue, and it was here:

axios({
    url: '/download-pdf-file',
    method: 'GET',
    responseType: 'blob',
    options: {            // here
        body: { dirpath } // here
    }
})

Laravel's Request arrow operator -> can't fetch a GET body sent through options (At least, not on $request->key fashion; see more about it here) thus making me download a corrupted file - it wasn't fetching any file on Laravel as it didn't get any path at all.

Here are the solution I came with:

As I want to get a file in a route that doesn't change except for the 1X at folder1X, I'm processing the path obtained and sending the 1X as a GET query param:

let folderNumber = dirpath.split('/')
folderNumber = folderNumber[folderNumber.length].replaceAll('/', '')

axios({
    url: '/download-pdf-file?folder=',
    method: 'GET',
    responseType: 'blob'
})

This way I don't pass the whole path to back-end and it's possible to get folderNumber by using $request->query():

public function downloadFileFromStorage(Request $request): mixed
{
    $folderNumber = $request->query('folderNumber');
    $folderPath = '/public/folder1/folder' . $folderNumber . '/';
    $files = Storage::allFiles($folderPath);
        
    return Storage::download($files[0], 'file-name.pdf');
}

In a nutshell:

  • To download files, use GET requests;
  • To send arguments to GET requests, use query parameters and fetch them with $request->query('keyname') (or find out another way. Good luck!);
  • Related