0

What is the best practice for taken data generated from a db and sending it from nodejs/express to browser such that it prompts the browser to down load it as a CSV. I am thinking to send it back a line at a time, converting it to a comma delimited row as I send, either manually or via something like fast-csv.

I've put together the following function but it only sends the data back as comma delimited data to the front end without prompting the browser to download. I'd want the browser to open a download dialog.

const csvExport = (
    sql: string,
    params: IGetListRequest,
    user: string,
    filename: string,
    res: Response
) => {
    try {
        // disable caching
        res.setHeader('Expires', 'Tue, 03 Jul 2001 06:00:00 GMT');
        res.setHeader(
            'Cache-Control',
            'max-age=0, no-cache, must-revalidate, proxy-revalidate'
        );
        res.setHeader('Last-Modified', `${Date()} GMT`);

        // force download
        res.setHeader('Content-Type', 'application/force-download');
        res.setHeader('Content-Type', 'application/octet-stream');
        res.setHeader('Content-Type', 'application/download');
        res.setHeader('Content-Type', 'text/csv');

        // disposition / encoding on response body
        res.setHeader(
            'Content-Disposition',
            `attachment; filename="${filename
                .replace(' ', '-')
                .toLowerCase()}.csv"`
        );
        res.setHeader('Content-Transfer-Encoding', 'binary');

        const csvStream = csv.format({ headers: true });
        csvStream.pipe(res).on('end', () => process.exit());

        const stmt = db.conn.prepare(sql);
        for (const rec of stmt.iterate({
            user,
            from: params.from.toISOString(),
            to: params.to ? params.to.toISOString() : params.from.toISOString(),
        })) {
            csvStream.write(rec);
        }
        csvStream.end();
        res.status(StatusCodes.OK).end();
    } catch (err) {
        if (err instanceof SqliteError) {
            throw new AppServerError({
                statusCode: StatusCodes.INTERNAL_SERVER_ERROR,
                message: err.message,
            });
        }
        throw err;
    }
};

Based on a second suggestion from @gunwin I also tried this

const csvExport = (
    sql: string,
    params: IGetListRequest,
    user: string,
    filename: string,
    res: Response
) => {
    try {
        res.type('text/csv');
        res.attachment(`${filename.replace(/[.: ]/g, '-').toLowerCase()}.csv`);

        const readStream = new stream.PassThrough();
        readStream.pipe(res).on('end', () => process.exit());

        const stmt = db.conn.prepare(sql);

        let i = 0;
        for (const rec of stmt.iterate({
            user,
            from: params.from.toISOString(),
            to: params.to ? params.to.toISOString() : params.from.toISOString(),
        })) {
            let csvData: Buffer;
            if (i < 1) {
                csvData = Buffer.from(
                    Object.keys(rec)
                        .map((field) => `"${field}"`)
                        .join(',') + '\n'
                );
                readStream.write(csvData);
                i++;
            }
            csvData = Buffer.from(
                Object.values(rec)
                    .map((field) => `"${field}"`)
                    .join(',') + '\n'
            );
            readStream.write(csvData);
        }

        readStream.end();
        res.status(StatusCodes.OK).end();
    } catch (err) {
        if (err instanceof SqliteError) {
            throw new AppServerError({
                statusCode: StatusCodes.INTERNAL_SERVER_ERROR,
                message: err.message,
            });
        }
        throw err;
    }
};
Templar
  • 11
  • 2
  • Does this answer your question? [Download a file from NodeJS Server using Express](https://stackoverflow.com/questions/7288814/download-a-file-from-nodejs-server-using-express) – LeoDog896 Nov 02 '22 at 21:01
  • Note the data is being dynamically generated and not from a file stored on the server. – Templar Nov 02 '22 at 21:07
  • Does this answer your question? [Node Express.js - Download file from memory - 'filename must be a string'](https://stackoverflow.com/questions/45922074/node-express-js-download-file-from-memory-filename-must-be-a-string) – gunwin Nov 02 '22 at 21:10
  • Based on that I changed to using streams like that post did but still get the same issue. No prompt to download file. Only getting the data sent to app but no download box appears. – Templar Nov 02 '22 at 23:11

0 Answers0