Download a file from NodeJS Server using Express

519,457

Solution 1

Update

Express has a helper for this to make life easier.

app.get('/download', function(req, res){
  const file = `${__dirname}/upload-folder/dramaticpenguin.MOV`;
  res.download(file); // Set disposition and send it.
});

Old Answer

As far as your browser is concerned, the file's name is just 'download', so you need to give it more info by using another HTTP header.

res.setHeader('Content-disposition', 'attachment; filename=dramaticpenguin.MOV');

You may also want to send a mime-type such as this:

res.setHeader('Content-type', 'video/quicktime');

If you want something more in-depth, here ya go.

var path = require('path');
var mime = require('mime');
var fs = require('fs');

app.get('/download', function(req, res){

  var file = __dirname + '/upload-folder/dramaticpenguin.MOV';

  var filename = path.basename(file);
  var mimetype = mime.lookup(file);

  res.setHeader('Content-disposition', 'attachment; filename=' + filename);
  res.setHeader('Content-type', mimetype);

  var filestream = fs.createReadStream(file);
  filestream.pipe(res);
});

You can set the header value to whatever you like. In this case, I am using a mime-type library - node-mime, to check what the mime-type of the file is.

Another important thing to note here is that I have changed your code to use a readStream. This is a much better way to do things because using any method with 'Sync' in the name is frowned upon because node is meant to be asynchronous.

Solution 2

Use res.download()

It transfers the file at path as an “attachment”. For instance:

var express = require('express');
var router = express.Router();

// ...

router.get('/:id/download', function (req, res, next) {
    var filePath = "/my/file/path/..."; // Or format the path using the `id` rest param
    var fileName = "report.pdf"; // The default name the browser will use

    res.download(filePath, fileName);    
});

Solution 3

For static files like pdfs, Word docs, etc. just use Express's static function in your config:

// Express config
var app = express().configure(function () {
    this.use('/public', express.static('public')); // <-- This right here
});

And then just put all your files inside that 'public' folder, for example:

/public/docs/my_word_doc.docx

And then a regular old link will allow the user to download it:

<a href="public/docs/my_word_doc.docx">My Word Doc</a>

Solution 4

Here's how I do it:

  1. create file
  2. send file to client
  3. remove file

Code:

let fs = require('fs');
let path = require('path');

let myController = (req, res) => {
  let filename = 'myFile.ext';
  let absPath = path.join(__dirname, '/my_files/', filename);
  let relPath = path.join('./my_files', filename); // path relative to server root

  fs.writeFile(relPath, 'File content', (err) => {
    if (err) {
      console.log(err);
    }
    res.download(absPath, (err) => {
      if (err) {
        console.log(err);
      }
      fs.unlink(relPath, (err) => {
        if (err) {
          console.log(err);
        }
        console.log('FILE [' + filename + '] REMOVED!');
      });
    });
  });
};

Solution 5

In Express 4.x, there is an attachment() method to Response:

res.attachment();
// Content-Disposition: attachment

res.attachment('path/to/logo.png');
// Content-Disposition: attachment; filename="logo.png"
// Content-Type: image/png
Share:
519,457
Thiago Miranda de Oliveira
Author by

Thiago Miranda de Oliveira

Updated on July 18, 2022

Comments

  • Thiago Miranda de Oliveira
    Thiago Miranda de Oliveira almost 2 years

    How can I download a file that is in my server to my machine accessing a page in a nodeJS server?

    I'm using the ExpressJS and I've been trying this:

    app.get('/download', function(req, res){
    
      var file = fs.readFileSync(__dirname + '/upload-folder/dramaticpenguin.MOV', 'binary');
    
      res.setHeader('Content-Length', file.length);
      res.write(file, 'binary');
      res.end();
    });
    

    But I can't get the file name and the file type ( or extension ). Can anyone help me with that?

  • Thiago Miranda de Oliveira
    Thiago Miranda de Oliveira over 12 years
    Thanks.. Is there a way to get this information from the fs.readFileSync? I'm using a static file in this example but I'll use this download api for any files, passing the name of it.
  • Capy
    Capy over 11 years
    Setting output filename works with res.setHeader('Content-disposition', 'attachment; filename=' + filename); tnx!
  • R J.
    R J. about 11 years
    how to download multiple documents using res.download() method.
  • loganfsmyth
    loganfsmyth about 11 years
    @RJ. If you have a question, create a new one, don't leave a comment.
  • frosty
    frosty over 10 years
    What if you want to use res.download, but change the content type header? I am trying to serve a chrome extension of my site, but the Chrome web store only allows this to work if the content type of the file is 'application/x-chrome-extension'. So, using res.download, is it possible to do that?
  • loganfsmyth
    loganfsmyth over 10 years
    Please create a new question, comments are for commenting.
  • ZachB
    ZachB over 10 years
    Note that it appears that res.download does not use streaming.
  • loganfsmyth
    loganfsmyth over 10 years
    @ZachB What makes you say that? It does as far as I can see.
  • ZachB
    ZachB over 10 years
    @loganfsmyth I was reading the source code and it just calls send. See similar observations posted here: stackoverflow.com/questions/13106096/…. Would be nice if I'm wrong.
  • loganfsmyth
    loganfsmyth over 10 years
    I don't know about older versions of Express, but in 3.x it just calls .sendfile, which uses streaming. github.com/visionmedia/express/blob/…
  • ZachB
    ZachB over 10 years
    @loganfsmyth Not sure what I was looking at, but you're right -- it does stream. Thanks for correcting me.
  • nembleton
    nembleton almost 9 years
    That works well for assets (although a dedicated serving proxy like nginx is recommended). But for anything that requires secured access, the accepted method is better. Generally speaking for docs and files containing information, I wouldn't recommend using the public method.
  • MalcolmOcean
    MalcolmOcean over 8 years
    you could add middleware to ensure that only appropriate users can access the files
  • MalcolmOcean
    MalcolmOcean over 8 years
    e.g. this.use('/topsecret', mGetLoggedInUser, mEnsureAccess, express.static('topsecret')) ...and then each request goes through mEnsureAccess. Of course, that means you'll need to be able to figure out a user's access level just based on the url of the secure document, or whatever.
  • user137717
    user137717 over 8 years
    @loganfsmyth How can I set the file path with res.download? The file I want to send is a directory level above and __dirname+'../otherdir' isn't correct.
  • loganfsmyth
    loganfsmyth over 8 years
    If you have a question, please ask it as a question, don't leave a comment.
  • Dana Woodman
    Dana Woodman about 8 years
    Express 4.x uses .set() instead of .setHeader() btw
  • Besto
    Besto over 7 years
    I couldn't do it with the first solution, but the express helper you edited in worked like a charm. Thanks!
  • summerNight
    summerNight over 6 years
    What if the data was coming in from a HTTP request instead of a file and we had to let users download the file in a streaming way?
  • Jossef Harush Kadouri
    Jossef Harush Kadouri over 6 years
    @summerNight - well, that is a different case than the question specified. search for nodejs proxy file download response for best practice
  • MikeSchem
    MikeSchem almost 6 years
    if you pass in the path, is this method safe for directory traversal attacks?
  • loganfsmyth
    loganfsmyth almost 6 years
    @MikeSchem It is not.
  • rohitwtbs
    rohitwtbs over 5 years
    @loganfsmyth I just used this code to get docx on client.I am using angular5 in clent. I request the docx from a http get request.But when i get the file browser throws error. syntax error: uexpected token P at JSON position 0.
  • loganfsmyth
    loganfsmyth over 5 years
    @rohitwtbs That sounds like you are treating to file as JSON when it isn't JSON. I can't really say why that would be.
  • user1063287
    user1063287 almost 5 years
    this is the only solution i have found in about two days of searching that works for my particular scenario of getting an audio file. the only thing is that i don't think res.download() works with $.ajax calls unfortunately - i had to use window.open("/api/get_audio_file");, see: stackoverflow.com/a/20177012
  • 1UC1F3R616
    1UC1F3R616 almost 4 years
    @summerNight i am still looking for this particular solution, if some link is there then plz help
  • summerNight
    summerNight almost 4 years
    @1UC1F3R616 I ended up solving the problem like this: router.get(API_PREFIX + '/file-download', function (req, res, next) { var file = process.env.FILE_DOWNLOAD_LOCATION + '/' + req.query.filename res.download(file); });
  • Jossef Harush Kadouri
    Jossef Harush Kadouri almost 4 years
    @summerNight and @1UC1F3R616 note that you are vulnerable to directory traversal attacks. for instance https://.../api?filename=../../../keys/my-secret-ssl-key.pem‌​. to avoid that, you need to validate the query param
  • Manny Alvarado
    Manny Alvarado about 2 years
    I am baffled at this answer. I started my server with "node server.js". It's running, but I can't seem to get this working. This should download a file into your downloads folder in your computer no? In my setup it just doesn't work. I've been trying to do this for an hour now.
  • Jhollman
    Jhollman almost 2 years
    this was the Answer i looking for, Simple and Efficient