448

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?

KARTHIKEYAN.A
  • 18,210
  • 6
  • 124
  • 133
  • 13
    Just FYI. For using in production, you are better off using node.js behind nginx, and make nginx handle static content. Apparently, it is much better suited for handling that. – Munim Apr 09 '13 at 08:14

7 Answers7

801

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.getType(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.

Norielle Cruz
  • 170
  • 5
  • 14
loganfsmyth
  • 156,129
  • 30
  • 331
  • 251
77

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);    
});
Jossef Harush Kadouri
  • 32,361
  • 10
  • 130
  • 129
  • 8
    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? – summerNight Sep 20 '17 at 15:37
  • 3
    @summerNight - well, that is a different case than the question specified. search for `nodejs proxy file download response` for best practice – Jossef Harush Kadouri Sep 20 '17 at 17:13
  • @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); });` – summerNight Jul 24 '20 at 16:24
  • 1
    @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 – Jossef Harush Kadouri Jul 25 '20 at 13:36
26

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>
jordanb
  • 1,975
  • 17
  • 8
  • 3
    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. – nembleton Jun 17 '15 at 04:44
  • 1
    you could add middleware to ensure that only appropriate users can access the files – MalcolmOcean Dec 11 '15 at 16:07
  • 1
    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. – MalcolmOcean Dec 11 '15 at 16:09
19

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!');
      });
    });
  });
};
Vedran
  • 1,113
  • 1
  • 17
  • 36
  • 2
    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()](https://expressjs.com/en/api.html#res.download) works with [$.ajax](https://api.jquery.com/jquery.ajax) calls unfortunately - i had to use `window.open("/api/get_audio_file");`, see: https://stackoverflow.com/a/20177012 – user1063287 Jul 14 '19 at 12:51
17

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
Benoit Blanchon
  • 13,364
  • 4
  • 73
  • 81
7
'use strict';

var express = require('express');
var fs = require('fs');
var compress = require('compression');
var bodyParser = require('body-parser');

var app = express();
app.set('port', 9999);
app.use(bodyParser.json({ limit: '1mb' }));
app.use(compress());

app.use(function (req, res, next) {
    req.setTimeout(3600000)
    res.header('Access-Control-Allow-Origin', '*');
    res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept,' + Object.keys(req.headers).join());

    if (req.method === 'OPTIONS') {
        res.write(':)');
        res.end();
    } else next();
});

function readApp(req,res) {
  var file = req.originalUrl == "/read-android" ? "Android.apk" : "Ios.ipa",
      filePath = "/home/sony/Documents/docs/";
  fs.exists(filePath, function(exists){
      if (exists) {     
        res.writeHead(200, {
          "Content-Type": "application/octet-stream",
          "Content-Disposition" : "attachment; filename=" + file});
        fs.createReadStream(filePath + file).pipe(res);
      } else {
        res.writeHead(400, {"Content-Type": "text/plain"});
        res.end("ERROR File does NOT Exists.ipa");
      }
    });  
}

app.get('/read-android', function(req, res) {
    var u = {"originalUrl":req.originalUrl};
    readApp(u,res) 
});

app.get('/read-ios', function(req, res) {
    var u = {"originalUrl":req.originalUrl};
    readApp(u,res) 
});

var server = app.listen(app.get('port'), function() {
    console.log('Express server listening on port ' + server.address().port);
});
KARTHIKEYAN.A
  • 18,210
  • 6
  • 124
  • 133
0

you can use res.sendFile()... the Sample-download.xlsx should be in the same directory as this function.

const downloadFile = (req,res) => {   
            var options = {
              root: path.join(__dirname),
            };
            
            let fileName = "Sample-download.xlsx";
            res.sendFile(fileName, options, function (err) {
              if (err) {
                console.log(err);
                return res.status(500).json({ success: false, message: "internal server error. please try again later" });
            
              } else {
                console.log("Sent:", fileName, "at", new Date().toString());
              }
            });
    }