38

I'm trying to package data in memory into a text file and send it to the user, triggering a file download.

I have the following code:

app.get('/download', function(request, response){

    fileType = request.query.fileType;
    fileName = ( request.query.fileName + '.' + fileType ).toString();
    fileData = request.query.fileData;

    response.set('Content-disposition', 'attachment; filename=' + fileName );
    response.set('Content-type', 'text/plain');

    var fileContents = new Buffer(fileData, "base64");

    response.status(200).download( fileContents );

});

It keeps throwing an error saying that Content-disposition's filename parameter must be a string. fileName is most certainly a string, so I'm not sure what is going on.

5
  • which line gives error? Commented Aug 28, 2017 at 15:07
  • Please declare your local variables such as fileType, fileName and fileData with let. Using accidental globals in a server is a recipe for disaster.
    – jfriend00
    Commented Aug 28, 2017 at 15:52
  • If you look at the code for res.download(), you can see that it just calls res.sendFile() so it is only built to send data from a file, not from memory. You will have to find a different way to send directly from memory or write to a temporary file first.
    – jfriend00
    Commented Aug 28, 2017 at 15:57
  • instead of using res.download() you should use res.send(Buffer) and it would work
    – anand
    Commented Dec 31, 2022 at 18:36
  • Just set the attachment header, per this thread stackoverflow.com/questions/21950049/…
    – Ben Jones
    Commented Jun 13 at 2:11

3 Answers 3

69

Update:

Thanks to @jfriend00's advice, it is better and more efficient to directly send Buffer to client as file, instead of saving it first in server disk.

To implement, stream.PassThrough() and pipe() can be used, here is an example:

var stream = require('stream');
//...
app.get('/download', function(request, response){
  //...
  var fileContents = Buffer.from(fileData, "base64");
  
  var readStream = new stream.PassThrough();
  readStream.end(fileContents);

  response.set('Content-disposition', 'attachment; filename=' + fileName);
  response.set('Content-Type', 'text/plain');

  readStream.pipe(response);
});

According to Express document, res.download() API is:

res.download(path [, filename] [, fn])

Transfers the file at path as an “attachment”. Typically, browsers will prompt the user for download. By default, the Content-Disposition header “filename=” parameter is path (this typically appears in the browser dialog). Override this default with the filename parameter.

Please note the first parameter of res.download() is a "path", which indicates the path of file in server that would be downloaded. In your code, the first parameter is a Buffer, that's why Node.js complain that "filename parameter must be a string" -- By default, the Content-Disposition header “filename=” parameter is path.

To make your code work using res.download(), you need to save the fileData in server as a file, and then invoke res.download() with that file's path:

var fs = require('fs');
//...
app.get('/download', function(request, response){
  //...
  var fileContents = Buffer.from(fileData, "base64");
  var savedFilePath = '/temp/' + fileName; // in some convenient temporary file folder
  fs.writeFile(savedFilePath, fileContents, function() {
    response.status(200).download(savedFilePath, fileName);
  });
});

Also, please note new Buffer(string[, encoding]) is deprecated now. It is better to use Buffer.from(string[, encoding]).

12
  • 6
    There should be a way to solve this problem without first writing the data to a file. Also, your code doesn't generate a unique filename that won't conflict with other requests and doesn't clean up the temporary file at an appropriate time.
    – jfriend00
    Commented Aug 28, 2017 at 15:54
  • 1
    @jfriend00 you are right. Write data to a file and eventually delete it is not efficient. I'm looking for a better solution, any suggestions? Commented Aug 28, 2017 at 15:57
  • @jfriend00 I've found a better solution which does not need saving-file-in-server. Thank you for your advice. Commented Aug 28, 2017 at 16:10
  • > I've found a better solution which does not need saving-file-in-server What was your solution @shaochuancs?
    – Maksim
    Commented Jul 19, 2018 at 19:32
  • Hi, @Maksim, please check the "update" of this answer. Commented Jul 20, 2018 at 7:18
23
app.get('/download', (request, response) => {
  const fileData = 'SGVsbG8sIFdvcmxkIQ=='
  const fileName = 'hello_world.txt'
  const fileType = 'text/plain'

  response.writeHead(200, {
    'Content-Disposition': `attachment; filename="${fileName}"`,
    'Content-Type': fileType,
  })

  const download = Buffer.from(fileData, 'base64')
  response.end(download)
})
2
  • You maybe having buffer only so put download as request.file, and you are good to go. I was looking for this for an hour now :) Thanks a lot
    – 1UC1F3R616
    Commented Jun 29, 2020 at 19:04
  • 2
    not working... I am getting Hello, World! in response but chrome is not opening the save file popup ...also not saving files automatically... Commented Jan 3, 2021 at 16:38
-3

I found this question via google. My problem was, that I wanted to send a generated index.html file directly from the memory, instead of writing it to the file system first.

This is how it works:

app.get('/', function (req, res) {
    //set the appropriate HTTP header
    res.setHeader('Content-Type', 'text/html');

    //send it to the client
    res.send('<h1>This is the response</h1>');
});

Source: https://stackoverflow.com/a/55032538

Not the answer you're looking for? Browse other questions tagged or ask your own question.