Allen More
Allen More

Reputation: 908

How do I stream a file attachment to a response in node.js?

Using node.js/Express.js, I would like to make a call to an API, write the response of that call to a file, then serve that file as an attachment to the client. The API call returns the correct data and that data is successfully being written to file; the issue is that when I try and stream that file from disk to the client, the file attachment that gets served is empty. Here is the body of my route handler:

// Make a request to an API, then pipe the response to a file. (This works)
request({
  url: 'http://localhost:5000/execute_least_squares',
  qs: query
}).pipe(fs.createWriteStream('./tmp/predictions/prediction_1.csv', 
  {defaultEncoding: 'utf8'}));

// Add some headers so the client know to serve file as attachment  
res.writeHead(200, {
    "Content-Type": "text/csv",
    "Content-Disposition" : "attachment; filename=" + 
    "prediction_1.csv"
});

// read from that file and pipe it to the response (doesn't work)
fs.createReadStream('./tmp/predictions/prediction_1.csv').pipe(res);

Question:

Why is the response just returning a blank document to the client?

Considerations:

C1. Is this issue happening because by the time the last line tries to read the file the process of writing it hasn't begun?

C1.a) Doesn't the fact that createWriteStream and createReadStream are both async ensure that createWriteStream will precede createReadStream in the event loop?

C2. Could it be that the 'data' event is not being triggered correctly? Doesn't pipe abstract this away for you?

Thanks for your input.

Upvotes: 0

Views: 4141

Answers (1)

imNotWhoYouSayIam
imNotWhoYouSayIam

Reputation: 100

Try this :

var writableStream = fs.createWriteStream('./tmp/predictions/prediction_1.csv',
{ defaultEncoding: 'utf8' })

request({
    url: 'http://localhost:5000/execute_least_squares',
     qs: query
}).pipe(writableStream);

//event that gets called when the writing is complete
writableStream.on('finish',() => {
    res.writeHead(200, {
    "Content-Type": "text/csv",
    "Content-Disposition" : "attachment; filename=" + 
    "prediction_1.csv"
});
    var readbleStream = fs.createReadStream('./tmp/predictions/prediction_1.csv')
   readableStream.pipe(res);
}

You should capture the on.('error') for both streams (write and read) so that you can return an suitable response (400 or else).

For more info :

Read the Node.js Stream documentation

Considerations :

Is this issue happening because by the time the last line tries to read the file the process of writing it hasn't begun?

A: Yes. Or another possibility is that the request has not finished.

Doesn't the fact that createWriteStream and createReadStream are both async ensure that createWriteStream will precede createReadStream in the event loop?

A: From what I have read in the docs createWriteStream and createReadStream are sync and they only return a WriteStream/ReadStream object.

Could it be that the 'data' event is not being triggered correctly? Doesn't pipe abstract this away for you?

A: If you are talking about this code:

request({
    url: 'http://localhost:5000/execute_least_squares',
    qs: query
}).pipe(fs.createWriteStream('./tmp/predictions/prediction_1.csv', 
   {defaultEncoding: 'utf8'}));

It works according to the request documentation. If you were talking about something else please explain it in more detail.

Upvotes: 5

Related Questions