node.jsreactjsadm-zipfile-saver

Downloading ZIP file using NodeJS and ReactJS


I am trying to download a zip file using a ReactJS client. The zip file is created at backend side (Node JS).

My React client does the following:

               {this.state.selectedRows.length > 0 &&
                  <>
                    <Tooltip title="Baixar arquivos" arrow>
                      <IconButton
                        onClick={() => {
                            const dcs = (!!this.state.selectedRows && this.state.selectedRows.length === 0) ? this.state.fls.map(f => f.doc_id ) :  this.state.selectedRows
                            this.props.api.post('/mediateca/downloadzipfile', { docs: dcs }).then(r => {
                            this.setState({ selectedRows: [] })
                            var blob = new Blob([r], { type: "application/octet-stream" });
                            saveAs(blob, "example.zip")
                          })
                        }}>
                        <FileDownload />
                      </IconButton>
                    </Tooltip>
                    <D size="0.5rem" />
                  </>
                }

If I print the post response ( "console.log(r)") I see a string like "PK<0x03><0x04><0x14>...", but longer (it seems to be the ASCII sequence of a zip file). I makes the download example.zip file with 238,2 kB. However, no zip application is able to open it.

More over, at backend Node JS side, I have the post route triggering this method:

    async downloadZIPfile(ctx) {
        let u = await ctx.auth.getUser()
        Log.create(!!u ? u.email : "null", ctx.request.url())
        const { docs } = ctx.request.all()
        const rdocs = !!docs && docs.length > 0 ? docs : []
        //Recupera os pdfs do banco de dados e salva um zip no /tmp
        if(rdocs.length > 0){
             var zip = new AdmZip()
             for(let x in rdocs){
                 const doc = await Documentos.findOne({ where: { id: rdocs[x] } })
                 if (!!doc) {
                     const file = await Arquivos.findOne({ where: { id: doc.arquivo_id } })
                     var filePath = "/tmp/"+rdocs[x]+".pdf"
                     zip.addFile(rdocs[x]+".pdf",file.binario)
                 }
             }
             var data = zip.toBuffer()
             var fileName = "flavio.zip"
             zip.writeZip("/tmp/"+fileName)
             ctx.response.header('Content-Type', 'application/octet-stream')
             ctx.response.header('Content-Disposition', `attachment; filename=${fileName}`)
             ctx.response.header('Content-Length', data.length)
             ctx.response.send(data);
        }
        else {
            ctx.response.status(404).send()
        }
        return { status: 'ok' }
    }
 }

Note that this method saves a flavio.zip 130,8KB file for debugging. It can be opened correctly by any zip application.

So, what is wrong with my React client POST response?

I am trying to download a zip file using a ReactJS client. The zip file is created at backend side (Node JS).


Solution

  • the issue might be with how you're handling the response in your ReactJS client. Instead of directly passing the response data to the saveAs function, you should create a new Blob object from the response data and then pass that to the saveAs function.

    this.props.api.post('/mediateca/downloadzipfile', { docs: dcs }, { responseType: 'arraybuffer' }).then(response => {
        const blob = new Blob([response.data], { type: 'application/octet-stream' })
        saveAs(blob, 'example.zip')
    })
    

    In this code, we're setting the responseType option to 'arraybuffer' so that the response data is returned as an ArrayBuffer object, which we can then use to create a new Blob object. We're passing this Blob object and the desired filename to the saveAs function to trigger the download.