6

I am building an app using node.js that needs to allow the user to download a .csv file.

The problem - the app does not send a file to the client as an attachment when the user clicks a button. HOWEVER, if the client goes directly to the API link, the file will download. E.g. - if the user goes to localhost:3000/api/exportmetric, the a file will be sent to the client as an attachment. But if that route is hit as an AJAX request, nothing happens.

User flow:

1) User clicks a button

2) App makes AJAX GET request to server

3) Server retrieves data from a database

4) Server parses the data into a .csv file

5) Server sends file back to the client to download as an attachment.

My code:

client.js

$("#export_velocity").click(function(e) {
    console.log('export to velocity hit');
    $.ajax({
        url: 'http://localhost:3001/api/exportmetric',
        type: 'GET',
        success: function(response) {
            console.log(response);
        },
        error: function(a, b, c) {
            console.log(a);
            console.log(b);
            console.log(c);
        }
    });
});

server.js

router.get('/api/exportmetric', function(req, res) {
    console.log('export metric hit');
    var fields = ['first_name', 'last_name', 'age'];
    var fieldNames = ['First Name', 'Last Name', 'Age??'];
    var people = [
      {
        "first_name": "George",
        "last_name": "Lopez",
        "age": "31"
      }, {
        "first_name": "John",
        "last_name": "Doe",
        "age": "15"
      }, {
        "first_name": "Jenna",
        "last_name": "Grassley",
        "age": "44"
      }
    ];

    json2csv({ data: people, fields: fields, fieldNames: fieldNames }, function(err, csv) {
      res.setHeader('Content-disposition', 'attachment; filename=file.csv');
      res.set('Content-Type', 'text/csv');
      console.log(csv)
      res.status(200).send(csv);
    });
});
Brendan Abel
  • 35,343
  • 14
  • 88
  • 118
Trung Tran
  • 13,141
  • 42
  • 113
  • 200
  • 3
    Do it without ajax. `Download CSV!` Stop overthinking it! – Kevin B Feb 02 '16 at 21:40
  • 1
    Right, but eventually I will need to use AJAX to send data to my server to query certain types of data that need to be downloaded into a csv file – Trung Tran Feb 02 '16 at 21:43
  • you can send data with GET and an anchor tag too. No less data than ajax would allow. – Kevin B Feb 02 '16 at 21:43
  • the problem with using ajax is you can't directly download the response of an ajax request. Instead, you'll have to make it a two step process where the server stores the file and sends a url to it to the client for download, then deletes it after download complete which gets a bit complicated. At that point it would be even easier to again drop ajax and do a form post or get to an iframe – Kevin B Feb 02 '16 at 21:45
  • I see, could you clarify `send data with GET and an anchor tag`? Or point me to where I can find more information about it? – Trung Tran Feb 02 '16 at 21:46
  • ahhh, perfect. thanks!! – Trung Tran Feb 02 '16 at 21:46

2 Answers2

10

There are basically two popular ways to download a file.

1. Set window.location

Setting window.location to the download url will download the file.

window.location = '/path/to/download?arg=1';

A slightly different version of this is to open a new tab with the download path

window.open('/path/to/download', '_self');

2. Virtual Link Click

With HTML5, you can specify the download attribute of a link. Clicking the link (even programmatically) will trigger a download of the url. The links don't even need to be part of the DOM, you can make them dynamically.

var link = document.createElement('a');
link.href = '/path/to/download';
link.download = 'local_filename.csv';
var e = document.createEvent('MouseEvents');
e.initEvent('click', true, true);
link.dispatchEvent(e);

This isn't supported in all browsers, so even if you want to use this method, you'll have to drop support for some browsers or fallback to the first method.

Luckily, this excellent answer references an awesome little js library that does all this already -- http://pixelscommander.com/polygon/downloadjs/#.VrGw3vkrKHv

downloadFile('/path/to/download');

2-Step Download

Another convention you'll often see is a two step download, where information is sent to the server at a known url, and the server sends back a generated url or id that can be used to download the file.

This can be useful if you want the url to be something that can be shared, or if you have to pass a lot of parameters to the download generator or just want to do it via a POST request.

$.ajax({
    type: 'POST',
    url: '/download/path/generator',
    data: {'arg': 1, 'params': 'foo'},
    success: function(data, textStatus, request) {
        var download_id = data['id'];
        // Could also use the link-click method.
        window.location = '/path/to/download?id=' + download_id;
    }
});
Community
  • 1
  • 1
Brendan Abel
  • 35,343
  • 14
  • 88
  • 118
0

to add to Brendan's answer I figured out a 3rd way that works :

1 - create a temporary form in the DOM

2 - fill the form with the data we would like to post (as in a post request)

3 - send the form

4 - delete the form from the DOM

Here is how I did it in JS

  $("#buybtn").click(function(){
    url = "localhost:8080/";
    // we create a form
    var form = document.createElement("form");
    // set the method to Post
    form.setAttribute("method", "post");
    // we set the action to be performed
    form.setAttribute("action", url + "api2");

    // the following variables contains the data to send
    params = {
        name : "name",
        age : "age"
    };

    // we iterate over the available fields in params
    // and set the data as if we are manually filling the form
    for(var key in params) {
        if(params.hasOwnProperty(key)) {
            var hiddenField = document.createElement("input");
            hiddenField.setAttribute("type", "hidden");
            hiddenField.setAttribute("name", key);
            hiddenField.setAttribute("value", params[key]);

            // we insert each element in the form
            form.appendChild(hiddenField);
        }
    }

    // we append the form to the DOM
    document.body.appendChild(form);
    // we submit the form
    form.submit();
    // we delete the created elements
    document.body.removeChild(form);
  });

Credits go to Rakesh Pai in this post : JavaScript post request like a form submit

I hope it works for you too !

iliasse
  • 247
  • 1
  • 7