144

Expressjs framework has a sendfile() method. How can I do that without using the whole framework?

I am using node-native-zip to create an archive and I want to send that to the user.

BinaryButterfly
  • 18,137
  • 13
  • 50
  • 91
andrei
  • 8,252
  • 14
  • 51
  • 66

5 Answers5

235

Here's an example program that will send myfile.mp3 by streaming it from disk (that is, it doesn't read the whole file into memory before sending the file). The server listens on port 2000.

[Update] As mentioned by @Aftershock in the comments, util.pump is gone and was replaced with a method on the Stream prototype called pipe; the code below reflects this.

var http = require('http'),
    fileSystem = require('fs'),
    path = require('path');

http.createServer(function(request, response) {
    var filePath = path.join(__dirname, 'myfile.mp3');
    var stat = fileSystem.statSync(filePath);

    response.writeHead(200, {
        'Content-Type': 'audio/mpeg',
        'Content-Length': stat.size
    });

    var readStream = fileSystem.createReadStream(filePath);
    // We replaced all the event handlers with a simple call to readStream.pipe()
    readStream.pipe(response);
})
.listen(2000);

Taken from http://elegantcode.com/2011/04/06/taking-baby-steps-with-node-js-pumping-data-between-streams/

Michelle Tilley
  • 157,729
  • 40
  • 374
  • 311
  • 1
    But I'm not streaming a file from the server, I create the archive – andrei Apr 06 '12 at 21:32
  • By "stream" I mean "send the file data to the connection as it's being read" as opposed to "read the whole file in memory then send all that data to the connection at once" (which is the typical naive approach). I _don't_ mean "stream the data from memory without it going to disk." The post I linked to explains in more detail. – Michelle Tilley Apr 06 '12 at 22:57
  • 7
    util.pump(readStream, response); is depreciated... use readStream.pipe(response); – Aftershock May 17 '13 at 10:38
  • 2
    This is not safe. See: http://stackoverflow.com/questions/20449055/node-js-stream-api-leak – Kr0e May 28 '14 at 12:42
  • @Abdul Not sure I understand your question; wanna [shoot me an email](http://michelletilley.net/contact.html)? – Michelle Tilley Mar 06 '16 at 17:04
  • Tried it but got `net::ERR_CONTENT_LENGTH_MISMATCH` error. – Ulysses Alves Apr 09 '17 at 01:18
  • @MichelleTilley: Is it possible to continuously read from a HTTP endpoint like `elasticsearch` and still write to a downloadable file by modifying your code? – summerNight Sep 19 '17 at 16:25
  • @MichelleTilley, so does this give data of zip in chunk formart?something like this? https://stackoverflow.com/questions/59280573/response-header-data-as-zip-in-node-js – Sudharshan Nair Dec 11 '19 at 13:01
19

You need use Stream to send file (archive) in a response, what is more you have to use appropriate Content-type in your response header.

There is an example function that do it:

const fs = require('fs');

// Where fileName is name of the file and response is Node.js Reponse. 
responseFile = (fileName, response) => {
    const filePath = "/path/to/archive.rar"; // or any file format

    // Check if file specified by the filePath exists
    fs.exists(filePath, function (exists) {
        if (exists) {
            // Content-type is very interesting part that guarantee that
            // Web browser will handle response in an appropriate manner.
            response.writeHead(200, {
                "Content-Type": "application/octet-stream",
                "Content-Disposition": "attachment; filename=" + fileName
            });
            fs.createReadStream(filePath).pipe(response);
            return;
        }
        response.writeHead(400, { "Content-Type": "text/plain" });
        response.end("ERROR File does not exist");
    });
}

The purpose of the Content-Type field is to describe the data contained in the body fully enough that the receiving user agent can pick an appropriate agent or mechanism to present the data to the user, or otherwise deal with the data in an appropriate manner.

"application/octet-stream" is defined as "arbitrary binary data" in RFC 2046, purpose of this content-type is to be saved to disk - it is what you really need.

"filename=[name of file]" specifies name of file which will be downloaded.

For more information please see this stackoverflow topic.

William Desportes
  • 1,412
  • 1
  • 22
  • 31
MikePtr
  • 1,661
  • 1
  • 16
  • 18
5

This helped me. It will start downloading the file as soon as you hit the /your-route route.

app.get("/your-route", (req, res) => {

         let filePath = path.join(__dirname, "youe-file.whatever");

         res.download(filePath);
}

Yes download is also an express method.

Debu Shinobi
  • 2,057
  • 18
  • 21
4

Bit Late but express has a helper for this to make life easier.

app.get('/download', function(req, res){
  const file = `${__dirname}/path/to/folder/myfile.mp3`;
  res.download(file); // Set disposition and send it.
});
Awais Ayub
  • 389
  • 3
  • 13
1

In case if you need to send gzipped on the fly file using Node.js natives only:

const fs = require('fs') // Node.js module
const zlib = require('zlib') // Node.js module as well

let sendGzip = (filePath, response) => {
    let headers = {
        'Connection': 'close', // intention
        'Content-Encoding': 'gzip',
        // add some headers like Content-Type, Cache-Control, Last-Modified, ETag, X-Powered-By
    }

    let file = fs.readFileSync(filePath) // sync is for readability
    let gzip = zlib.gzipSync(file) // is instance of Uint8Array
    headers['Content-Length'] = gzip.length // not the file's size!!!

    response.writeHead(200, headers)
    
    let chunkLimit = 16 * 1024 // some clients choke on huge responses
    let chunkCount = Math.ceil(gzip.length / chunkLimit)
    for (let i = 0; i < chunkCount; i++) {
        if (chunkCount > 1) {
            let chunk = gzip.slice(i * chunkLimit, (i + 1) * chunkLimit)
            response.write(chunk)
        } else {
            response.write(gzip)
        }
    }
    response.end()
}
  • 1
    Please don't post only code as an answer, but also provide an explanation what your code does and how it solves the problem of the question. Answers with an explanation are usually more helpful and of better quality, and are more likely to attract upvotes. – Tyler2P Nov 26 '21 at 19:20