0

I am using ExpressJS to develop a web app.

There is a Create button on the page. What I want to achieve is, when the button is clicked, it sends a Post/Get request to the server side, and the server side then triggers a process to generate a CSV file and send it back to the client side for download.

I am thinking of using json2csv.

Client side:

$.ajax({
            type: "POST",
            url: "/incidents/createTable",
            success: function() {
                // HOW TO GET THE RETURNED DATA TO A DOWNLOADABLE FILE?
            }
        });

Server side and incidents router (the code snippet that follows was copied from the json2csv official npmjs page):

const { AsyncParser } = require('json2csv');
// THIS FUNCTION SHOULD GENERATE A CSV FILE IN MEMORY
router.post("/createTable", async function(req, res, next) {
    console.log("flag 1");  // For flagging

    const fields = ['field1', 'field2', 'field3'];
    const opts = { fields };
    const transformOpts = { highWaterMark: 8192 };

    const asyncParser = new AsyncParser(opts, transformOpts);

    console.log("flag 2");  // For flagging
    let csv = '';
    asyncParser.processor
    .on('data', chunk => (csv += chunk.toString()))
    .on('end', () => res.send(csv))
    .on('error', err => console.error(err));
});

When I ran the web app and clicked the Create button, the server hung there, it passed "flag 2" and never went pass the asyncParser.processor. On the client side, the POST request was also hung there and with no status code.

alextc
  • 3,206
  • 10
  • 63
  • 107
  • Do you mean that 'flag 1' is logged but no 'flag 2' ? – BENARD Patrick Aug 23 '19 at 07:53
  • Sorry for typo. It logged `flag 2` but not `asyncParser.processor`. – alextc Aug 23 '19 at 07:57
  • Ok but which data are you given the to processor ? – BENARD Patrick Aug 23 '19 at 07:59
  • the code does not look complete, you are not giving any data to processor. on the doc which you copied the code, the next line should read `asyncParser.input.push(data); // This data might come from an HTTP request, etc.` but you dont have a corresponding line – Eric Wong Aug 23 '19 at 08:00
  • You could add [res.attachment('data.csv')](https://expressjs.com/en/4x/api.html#res.attachment) to make the browser download the file but to make that work you can't use ajax. you have to submit a `
    ` the other option is to use something like [FileSaver.js](https://github.com/eligrey/FileSaver.js/) or [StreamSaver](https://github.com/jimmywarting/StreamSaver.js). You could also generate the csv on the client side and trigger a download
    – Endless Aug 23 '19 at 08:10
  • Sorry for the confusion. I wanted to have the `createTable` function to generate a CSV but in memory and send back to the client side (via ajax success?). Then the user will be able to download the file. – alextc Aug 23 '19 at 08:12
  • if you want to use ajax then you have to generate a ObjectURL from a blob, attach it to a link, add download attribute and trigger a click on it, something which streamsaver and filesaver dose for you. `res.attachment` is useless if it's not navigated to – Endless Aug 23 '19 at 08:15
  • @Endless. I would prefer to create the csv file in memory as the file will be rather small than on the server. – alextc Aug 23 '19 at 08:15
  • sure you can do that but i was explaining more of how you can save the file not generating it – Endless Aug 23 '19 at 08:19

2 Answers2

1

Finally worked out a solution after doing a lot digging.

Server side:

var stream = require('stream');
//...
router.post("/createTable", async function(req, res, next) {
    var fileContents = Buffer.from(JSON.stringify({
        sampleTime: '1450632410296',
        sampleData: '1234567890'
    }));

    var readStream = new stream.PassThrough();
    readStream.end(fileContents);

    res.set('Content-disposition', 'attachment; filename=' + "download.csv");
    res.set('Content-Type', 'text/csv');

    readStream.pipe(res);
});

Client side:

 $.ajax({
        type: "POST",
        url: "/incidents/createTable",
        success: function(result) {
            var blob=new Blob([result], {type: 'text/csv'});
            var link=document.createElement('a');

            link.style = "display: none";
            document.body.appendChild(link);

            var url = window.URL.createObjectURL(blob);
            link.href = url;
            console.log(url);
            link.download="download.csv";
            link.click();
            window.URL.revokeObjectURL(url);
        }
    });
alextc
  • 3,206
  • 10
  • 63
  • 107
0

You're missing one part, which is providing the data. Parser won't do anything until you do that. This is also hunging up a request, because res.send will never be reached.

Right from the docs:

asyncParser.input.push(data); // This data might come from an HTTP request, etc.
asyncParser.input.push(null); // Sending `null` to a stream signal that no more data is expected and ends it.

Here is complete code that will produce

"field1","field2","field3"
1,2,3

on GET /createTable

const { AsyncParser } = require('json2csv');
const express = require('express');

const app = express();

app.get("/createTable", async function(req, res, next) {
    console.log("flag 1");  // For flagging

    const fields = ['field1', 'field2', 'field3'];
    const opts = { fields };
    const transformOpts = { highWaterMark: 8192 };

    const asyncParser = new AsyncParser(opts, transformOpts);

    console.log("flag 2");  // For flagging
    let csv = '';
    asyncParser.processor
      .on('data', chunk => (csv += chunk.toString()))
      .on('end', () => res.send(csv))
      .on('error', err => console.error(err));

    asyncParser.input.push('{ "field1": 1, "field2": 2, "field3": 3 }');
    asyncParser.input.push(null); // Sending `null` to a stream signal that no more data is expected and ends it.
});

app.listen(3000);
pr0gramist
  • 8,305
  • 2
  • 36
  • 48
  • Thanks. I tried your code. The client side did print out `"field1","field2","field3" 1,2,3`. But this is not what I wanted to achieve. I would like to have the output as a csv file for user to download as stated in the subject title. – alextc Aug 23 '19 at 08:10
  • 1
    There are a lot of ways to do that. https://stackoverflow.com/questions/13405129/javascript-create-and-save-file – pr0gramist Aug 23 '19 at 08:24