0

I have the following Controller in my nestjs app:

  @ApiPost('download/:id', { type: Blob, description: 'Download file from URL' })
  async downloadFile(@Param() params: InputIdDto, @Res() res: Response) {
    const file = await this._fileService.findOne({ where: { id: params.id } });
    const { buffer } = await this._fileService.downloadFile(file.url);

    console.log(buffer);

    res.set({
      'Content-Type': 'application/octet-stream',
      'Content-Disposition': `attachment; filename=${file.originalname}`, // Not found on client,
      'Content-Length': buffer.length,
    });

    res.end(buffer);
  }

The buffer in the console looks like this:

<Buffer 25 50 44 46 2d 31 2e 34 0a 25 d3 eb e9 e1 0a 31 20 30 20 6f 62 6a 0a 3c 3c 2f 54 69 74 6c 65 20 28 4d 61 74 68 57 69 7a 61 72 64 73 29 0a 2f 43 72 65 ... 15140 more bytes>

In my NextJS APP I do the following:


export const downloadFile = (data: { id: string; filename: string }) =>
  axios
    .post<Blob>(`${process.env.NEXT_PUBLIC_API_URL}/file/download/${data.id}`, {
      responseType: 'blob',
    })
    .then((res: AxiosResponse<Blob>) => res.data);


export const useDownloadFile = () => {
  return useMutation({
    mutationFn: downloadFile,
    onSuccess(data, variables, context) {
      console.log('Blob', data);
      const url = window.URL.createObjectURL(new Blob([data]));
      console.log(data, url);

      const a = document.createElement('a');
      a.href = url;
      a.download = 'download-file';
      document.body.appendChild(a);
      a.click();
      a.remove();
    },
    onError(error, variables, context) {
      console.log('Error', error);
    },
  });
};

In the APP console, the result looks like:

Blob %PDF-1.4
%����
1 0 obj
<</Title (Title)
/Creator (Mozilla/5.0 \(Windows NT 10.0; Win64; x64\) AppleWebKit/537.36 \(KHTML, like Gecko\) HeadlessChrome/125.0.0.0 Safari/537.36)
/Producer (Skia/PDF m125)
/CreationDate (D:20240608210121+00'00')
/ModDate (D:20240608210121+00'00')>>
endobj
3 0 obj
<</ca 1
/BM /Normal>>
endobj
6 0 obj
<</Filter /FlateDecode
...

The file is downlaoded but I cant open it or its empty (PDF). Any other extension does not work as well. What is wrong with my code?

1
  • So what should I change in my code? Something in backend or frontend?
    – Korer
    Commented Jun 9 at 0:57

1 Answer 1

0

You're sending a node Buffer instance and expecting a Blob on your frontend and those are difference things. You are also setting the header to be a stream and well, not sending a stream.

If your file is too large and definitely needs to be streamed, you have to actually send a stream instance from Nest to your frontend instead of a node Buffer. And then your axios responseType will be expected to be "stream". That being said, I honestly don't know if it will work with that anchor.click etc method but at least your frontend will be in sync with your backend regarding the file transfer.

Now if your file is not that big you can encode it as base64. It works perfectly well with anchor.click and you don't need to take control of the Request object like you did. Then you use a function to transform the received base64 encoded file into a Blob and proceed to download it. Here is a snippet I have used in a project not a long while ago.

function downloadFile(blob) {
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = `file-${new Date().getTime()}.xlsx`;
  document.body.appendChild(a); // we need to append the element to the dom -> otherwise it will not work in firefox
  a.click();
  a.remove(); //afterwards we remove the element again
}

function base64toBlob(base64Data, contentType) {
  contentType = contentType || '';
  var sliceSize = 1024;
  var byteCharacters = atob(base64Data);
  var bytesLength = byteCharacters.length;
  var slicesCount = Math.ceil(bytesLength / sliceSize);
  var byteArrays = new Array(slicesCount);

  for (var sliceIndex = 0; sliceIndex < slicesCount; ++sliceIndex) {
    var begin = sliceIndex * sliceSize;
    var end = Math.min(begin + sliceSize, bytesLength);

    var bytes = new Array(end - begin);
    for (var offset = begin, i = 0; offset < end; ++i, ++offset) {
      bytes[i] = byteCharacters[offset].charCodeAt(0);
    }
    byteArrays[sliceIndex] = new Uint8Array(bytes);
  }
  return new Blob(byteArrays, { type: contentType });
}

  const blob = base64toBlob(data);

  downloadFile(blob);

As for node Buffer to base64, just .toString("base64") it. More here.

Not the answer you're looking for? Browse other questions tagged or ask your own question.