521

How do I download a file with Node.js without using third-party libraries?

I don't need anything special. I only want to download a file from a given URL, and then save it to a given directory.

VB_Dojnaz
  • 332
  • 5
  • 16
greepow
  • 5,623
  • 4
  • 14
  • 8
  • 6
    *"download a file with node.js"* - do you mean *upload* to the server? or retrieve a file from a remote server using your server? or serve a file to a client for download from your node.js server? – Joseph Aug 14 '12 at 02:23
  • 94
    "I only want to download a file from a given url, and then save it to a given directory," it seems pretty clear. :) – Michelle Tilley Aug 14 '12 at 02:26
  • 46
    Joseph is making an incorrect assertion that all node processes are server processes – lededje Dec 08 '13 at 14:36
  • 2
    @lededje What prevents a server process from downloading a file and saving it to a directory on a server? It is prefectly doable. – Gherman Feb 21 '20 at 08:51

30 Answers30

698

You can create an HTTP GET request and pipe its response into a writable file stream:

const http = require('http'); // or 'https' for https:// URLs
const fs = require('fs');

const file = fs.createWriteStream("file.jpg");
const request = http.get("http://i3.ytimg.com/vi/J---aiyznGQ/mqdefault.jpg", function(response) {
  response.pipe(file);
});

If you want to support gathering information on the command line--like specifying a target file or directory, or URL--check out something like Commander.

Michelle Tilley
  • 149,782
  • 38
  • 355
  • 303
  • 4
    I got the following console output when I ran this script: `node.js:201 throw e; // process.nextTick error, or 'error' event on first tick ^ Error: connect ECONNREFUSED at errnoException (net.js:646:11) at Object.afterConnect [as oncomplete] (net.js:637:18) `. – Anderson Green Jan 01 '13 at 06:15
  • Try using a different URL on the `http.get` line; maybe `http://i3.ytimg.com/vi/J---aiyznGQ/mqdefault.jpg` (and replace `file.png` with `file.jpg`). – Michelle Tilley Jan 01 '13 at 06:18
  • Why would this error occur in the first place? The image is still online (http://im.glogster.com/media/2/5/24/10/5241033.png), so why does it produce an error? I'm still confused. – Anderson Green Jan 01 '13 at 06:20
  • I thought perhaps you were on a network that was blocking access to that domain; the error message by itself wasn't really much to go on, so I took a guess. :) – Michelle Tilley Jan 01 '13 at 06:21
  • My network isn't blocking access to that domain - I'm able to access it from my web browser (Google Chrome), but I can't access it from node.js using the method above. – Anderson Green Jan 01 '13 at 06:22
  • I'm sorry, but I tried the code again just now using both Node v0.8.5 and also Node v0.8.16 with no issues--I'm not sure why your connection is being refused, but I don't believe it has anything to do with Node specifically. – Michelle Tilley Jan 01 '13 at 06:26
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/21978/discussion-between-anderson-green-and-brandon-tilley) – Anderson Green Jan 01 '13 at 07:12
  • Someone who actually explains whats going on thank you, I saw this code so many time but for somereason your answer made the most sense – johnny 5 Jan 07 '15 at 00:13
  • 10
    Does this code close the file properly when the script ends or would it lose data? – philk May 20 '15 at 19:29
  • "Something went wrong" is saved into the file when I try this – quantumpotato May 05 '18 at 03:33
  • 2
    @quantumpotato Take a look at the response you're getting back from your request – Michelle Tilley May 08 '18 at 18:30
  • @jiehanzheng that aint just any ol cat! Thats keyboard cat! https://www.youtube.com/watch?v=J---aiyznGQ – Josh Mc Jun 15 '18 at 11:36
  • 10
    This depends upon the req url type if you are requesting `https` you must use `https` otherwise it will throw error. – Krishnadas PC Sep 19 '18 at 13:20
  • it there any method to get its progress. how much it is downloaded – Dipanshu Mahla Aug 28 '20 at 11:31
  • 1
    This code does not properly close the fs write. It is not noticable on very small files but it is definitely obvious on larger downloads. – Ethan Keiley Sep 01 '20 at 14:46
  • 3
    @EthanKeiley why do you say that it isn't closed properly? By default `createWriteStream` will set `autoClose` to `true` and `readable.pipe` will call `end()` on the writeable when the readable ends. – steinybot Jan 28 '21 at 20:34
  • For everyone that copied this function and nothing happend, or a file was created without getting it filled with more data than 0 kb: I had to do something with the file before it would be filled. For example, adding: `var unit_test_file_content = fs.readFileSync('/somedirectory//file.jpg'); console.log(unit_test_file_content)` ensured the image actually got downloaded. – a.t. Feb 16 '21 at 16:23
  • so any news on this ? I get "Error: connect ECONNREFUSED 127.0.0.1:443" – Amir Torkashvand Feb 16 '21 at 18:44
551

Don't forget to handle errors! The following code is based on Augusto Roman's answer.

var http = require('http');
var fs = require('fs');

var download = function(url, dest, cb) {
  var file = fs.createWriteStream(dest);
  var request = http.get(url, function(response) {
    response.pipe(file);
    file.on('finish', function() {
      file.close(cb);  // close() is async, call cb after close completes.
    });
  }).on('error', function(err) { // Handle errors
    fs.unlink(dest); // Delete the file async. (But we don't check the result)
    if (cb) cb(err.message);
  });
};
Vince Yuan
  • 10,083
  • 2
  • 30
  • 26
  • 2
    @vince-yuan is `download()` itself `pipe`able? – rasx May 13 '15 at 20:53
  • @theGrayFox Because the code in this answer is way longer than the accepted one. :) – pootow Sep 28 '15 at 02:33
  • 2
    @VinceYuan the callback is confusing me. if I now invoke `download()`, how would I do it? What would I place as the `cb` argument? I have the `download('someURI', '/some/destination', cb)` but don't understand what to put in the cb – Abdul Mar 06 '16 at 22:43
  • 2
    @Abdul Sounds like you are very new to node.js/javascript. Take a look at this tutorial: http://www.tutorialspoint.com/nodejs/nodejs_callbacks_concept.htm It's not complex. – Vince Yuan Mar 07 '16 at 00:59
  • Great script.But when the file list is long, a error happens: Too many open files in system – 令狐葱 Aug 03 '16 at 02:30
  • 1
    @Abdul maybe it would be good if you share with the rest of the class what you have figured out ? – Curtwagner1984 Oct 25 '16 at 08:54
  • 6
    Is there a way to see the speed of the download? Like can track how many mb/s? Thanks! – Tino Caer Apr 19 '17 at 18:24
  • I'm not sure this is an up-to-date solution. I don't see anything about `close` being async and taking a callback in the node docs. – AjaxLeung Feb 03 '19 at 06:51
  • Looks like the right way to do this is with `end` now. https://github.com/nodejs/node/issues/2950#issuecomment-278094170 – AjaxLeung Feb 03 '19 at 06:58
  • can we specify the destination folder? –  Sep 02 '19 at 01:48
  • fs.unlink(dest) throws if not passing it a second argument (a callback) node v10.15.0 – YardenST Sep 24 '19 at 07:06
164

As Michelle Tilley said, but with the appropriate control flow:

var http = require('http');
var fs = require('fs');

var download = function(url, dest, cb) {
  var file = fs.createWriteStream(dest);
  http.get(url, function(response) {
    response.pipe(file);
    file.on('finish', function() {
      file.close(cb);
    });
  });
}

Without waiting for the finish event, naive scripts may end up with an incomplete file.

Edit: Thanks to @Augusto Roman for pointing out that cb should be passed to file.close, not called explicitly.

gfxmonk
  • 7,932
  • 4
  • 37
  • 52
  • 5
    the callback is confusing me. if I now invoke `download()`, how would I do it? What would I place as the `cb` argument? I have the `download('someURI', '/some/destination', cb)` but don't understand what to put in the cb – Abdul Mar 06 '16 at 22:43
  • 3
    @Abdul You specify the callback with a function only if you need to do something when the file has been successfully fetched. – CatalinBerta May 17 '16 at 10:57
74

Speaking of handling errors, it's even better listening to request errors too. I'd even validate by checking response code. Here it's considered success only for 200 response code, but other codes might be good.

const fs = require('fs');
const http = require('http');

const download = (url, dest, cb) => {
    const file = fs.createWriteStream(dest);

    const request = http.get(url, (response) => {
        // check if response is success
        if (response.statusCode !== 200) {
            return cb('Response status was ' + response.statusCode);
        }

        response.pipe(file);
    });

    // close() is async, call cb after close completes
    file.on('finish', () => file.close(cb));

    // check for request error too
    request.on('error', (err) => {
        fs.unlink(dest);
        return cb(err.message);
    });

    file.on('error', (err) => { // Handle errors
        fs.unlink(dest); // Delete the file async. (But we don't check the result) 
        return cb(err.message);
    });
};

Despite the relative simplicity of this code, I would advise to use the request module as it handles many more protocols (hello HTTPS!) which aren't natively supported by http.

That would be done like so:

const fs = require('fs');
const request = require('request');

const download = (url, dest, cb) => {
    const file = fs.createWriteStream(dest);
    const sendReq = request.get(url);

    // verify response code
    sendReq.on('response', (response) => {
        if (response.statusCode !== 200) {
            return cb('Response status was ' + response.statusCode);
        }

        sendReq.pipe(file);
    });

    // close() is async, call cb after close completes
    file.on('finish', () => file.close(cb));

    // check for request errors
    sendReq.on('error', (err) => {
        fs.unlink(dest);
        return cb(err.message);
    });

    file.on('error', (err) => { // Handle errors
        fs.unlink(dest); // Delete the file async. (But we don't check the result)
        return cb(err.message);
    });
};
Buzut
  • 3,955
  • 2
  • 40
  • 48
  • 2
    The request module just works straight for HTTPs. Cool! – Thiago C. S Ventura Jun 30 '16 at 16:07
  • @ventura yep, btw, there's also the native [https](https://nodejs.org/api/https.html) module that now can handle secure connections. – Buzut Jul 01 '16 at 17:40
  • It's more error prone without a doubt. Anyway, in any case where using request module is an option, I'd advise it as it's way higher level and thus, easier and efficient. – Buzut Jul 02 '16 at 07:23
  • @Buzut Your request code calls callback twice for success. Once on 'request' and once on 'finish' of file. Usual callback expectation is to be called once. – Alex Feb 06 '17 at 18:03
  • 2
    @Alex, nope, this is an error message and there's a return. So if `response.statusCode !== 200` the cb on `finish` will never be called. – Buzut Feb 06 '17 at 23:53
  • It calls `finish` callback when status is 403, for me. Maybe you only want to start piping when response code is confirmed to be 200 – Azmisov Jan 09 '18 at 00:02
  • 1
    Thank you for showing example using request module. – Pete Alvin Jul 04 '18 at 09:20
  • This code should definitely only start piping once the response have been confirmed to be a 200. – schankam Nov 27 '18 at 07:05
49

gfxmonk's answer has a very tight data race between the callback and the file.close() completing. file.close() actually takes a callback that is called when the close has completed. Otherwise, immediate uses of the file may fail (very rarely!).

A complete solution is:

var http = require('http');
var fs = require('fs');

var download = function(url, dest, cb) {
  var file = fs.createWriteStream(dest);
  var request = http.get(url, function(response) {
    response.pipe(file);
    file.on('finish', function() {
      file.close(cb);  // close() is async, call cb after close completes.
    });
  });
}

Without waiting for the finish event, naive scripts may end up with an incomplete file. Without scheduling the cb callback via close, you may get a race between accessing the file and the file actually being ready.

MikeL
  • 4,866
  • 38
  • 40
Augusto Roman
  • 619
  • 6
  • 4
24

Maybe node.js has changed, but it seems there are some problems with the other solutions (using node v8.1.2):

  1. You don't need to call file.close() in the finish event. Per default the fs.createWriteStream is set to autoClose: https://nodejs.org/api/fs.html#fs_fs_createwritestream_path_options
  2. file.close() should be called on error. Maybe this is not needed when the file is deleted (unlink()), but normally it is: https://nodejs.org/api/stream.html#stream_readable_pipe_destination_options
  3. Temp file is not deleted on statusCode !== 200
  4. fs.unlink() without a callback is deprecated (outputs warning)
  5. If dest file exists; it is overridden

Below is a modified solution (using ES6 and promises) which handles these problems.

const http = require("http");
const fs = require("fs");

function download(url, dest) {
    return new Promise((resolve, reject) => {
        const file = fs.createWriteStream(dest, { flags: "wx" });

        const request = http.get(url, response => {
            if (response.statusCode === 200) {
                response.pipe(file);
            } else {
                file.close();
                fs.unlink(dest, () => {}); // Delete temp file
                reject(`Server responded with ${response.statusCode}: ${response.statusMessage}`);
            }
        });

        request.on("error", err => {
            file.close();
            fs.unlink(dest, () => {}); // Delete temp file
            reject(err.message);
        });

        file.on("finish", () => {
            resolve();
        });

        file.on("error", err => {
            file.close();

            if (err.code === "EEXIST") {
                reject("File already exists");
            } else {
                fs.unlink(dest, () => {}); // Delete temp file
                reject(err.message);
            }
        });
    });
}
Bjarke Pjedsted
  • 308
  • 3
  • 7
  • 2
    Two comments on this: 1) it should probably reject Error objects, not strings, 2) fs.unlink will quietly swallow errors which might not necessarily be what you want to do – Richard Nienaber May 22 '18 at 10:29
  • 1
    This works great! And if your URLs use HTTPS, just substitute `const https = require("https");` for `const http = require("http");` – Russ Jun 14 '19 at 22:50
17

for those who came in search of es6-style promise based way, I guess it would be something like:

var http = require('http');
var fs = require('fs');

function pDownload(url, dest){
  var file = fs.createWriteStream(dest);
  return new Promise((resolve, reject) => {
    var responseSent = false; // flag to make sure that response is sent only once.
    http.get(url, response => {
      response.pipe(file);
      file.on('finish', () =>{
        file.close(() => {
          if(responseSent)  return;
          responseSent = true;
          resolve();
        });
      });
    }).on('error', err => {
        if(responseSent)  return;
        responseSent = true;
        reject(err);
    });
  });
}

//example
pDownload(url, fileLocation)
  .then( ()=> console.log('downloaded file no issues...'))
  .catch( e => console.error('error while downloading', e));
mido
  • 20,728
  • 10
  • 84
  • 109
  • 2
    `responseSet` flag caused, for some reason which I hadn't had the time to investigate, my file to be downloaded incompletely. No errors popped up but the .txt file I was populating had half of the rows that needed to be there. Removing the logic for the flag fixed it. Just wanted to point that out if someone had the issues with the approach. Still, +1 – Milan Velebit Oct 05 '18 at 13:46
16

Solution with timeout, prevent memory leak :

The following code is based on Brandon Tilley's answer :

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

var request = http.get("http://example12345.com/yourfile.html", function(response) {
    if (response.statusCode === 200) {
        var file = fs.createWriteStream("copy.html");
        response.pipe(file);
    }
    // Add timeout.
    request.setTimeout(12000, function () {
        request.abort();
    });
});

Don't make file when you get an error, and prefere to use timeout to close your request after X secondes.

A-312
  • 10,203
  • 4
  • 38
  • 66
  • 1
    this is only a file, has no protocol or server to download from... `http.get("http://example.com/yourfile.html",function(){})` – mjz19910 May 01 '18 at 04:06
  • Is there a memory leak in this answer: https://stackoverflow.com/a/22793628/242933? – ma11hew28 Oct 29 '18 at 13:30
  • You can add timeout like I did in `http.get`. The memory leak is only if the file take too long to be downloaded. – A-312 Nov 02 '18 at 08:11
7

Vince Yuan's code is great but it seems to be something wrong.

function download(url, dest, callback) {
    var file = fs.createWriteStream(dest);
    var request = http.get(url, function (response) {
        response.pipe(file);
        file.on('finish', function () {
            file.close(callback); // close() is async, call callback after close completes.
        });
        file.on('error', function (err) {
            fs.unlink(dest); // Delete the file async. (But we don't check the result)
            if (callback)
                callback(err.message);
        });
    });
}
Feel Physics
  • 2,773
  • 4
  • 22
  • 36
7

Hi,I think you can use child_process module and curl command.

const cp = require('child_process');

let download = async function(uri, filename){
    let command = `curl -o ${filename}  '${uri}'`;
    let result = cp.execSync(command);
};


async function test() {
    await download('http://zhangwenning.top/20181221001417.png', './20181221001417.png')
}

test()

In addition,when you want download large、multiple files,you can use cluster module to use more cpu cores.

wenningzhang
  • 101
  • 1
  • 5
7

I prefer request() because you can use both http and https with it.

request('http://i3.ytimg.com/vi/J---aiyznGQ/mqdefault.jpg')
  .pipe(fs.createWriteStream('cat.jpg'))
mixdev
  • 2,454
  • 2
  • 27
  • 23
  • Looks like Request has been deprecated https://github.com/request/request/issues/3142 `"As of Feb 11th 2020, request is fully deprecated. No new changes are expected to land. In fact, none have landed for some time."` – Michael Kubler May 29 '20 at 11:57
6
const download = (url, path) => new Promise((resolve, reject) => {
http.get(url, response => {
    const statusCode = response.statusCode;

    if (statusCode !== 200) {
        return reject('Download error!');
    }

    const writeStream = fs.createWriteStream(path);
    response.pipe(writeStream);

    writeStream.on('error', () => reject('Error writing to file!'));
    writeStream.on('finish', () => writeStream.close(resolve));
});}).catch(err => console.error(err));
kayz1
  • 6,652
  • 3
  • 48
  • 54
6

✅So if you use pipeline, it would close all other streams and make sure that there are no memory leaks.

Working example:

const http = require('http');
const { pipeline } = require('stream');
const fs = require('fs');

const file = fs.createWriteStream('./file.jpg');

http.get('http://via.placeholder.com/150/92c952', response => {
  pipeline(
    response,
    file,
    err => {
      if (err)
        console.error('Pipeline failed.', err);
      else
        console.log('Pipeline succeeded.');
    }
  );
});

From my answer to "What's the difference between .pipe and .pipeline on streams".

Wai Ha Lee
  • 7,664
  • 52
  • 54
  • 80
Idan Dagan
  • 6,637
  • 3
  • 26
  • 37
5

You can use https://github.com/douzi8/ajax-request#download

request.download('http://res.m.ctrip.com/html5/Content/images/57.png', 
  function(err, res, body) {}
);
douzi
  • 91
  • 1
  • 1
5

Download using promise, which resolve a readable stream. put extra logic to handle the redirect.

var http = require('http');
var promise = require('bluebird');
var url = require('url');
var fs = require('fs');
var assert = require('assert');

function download(option) {
    assert(option);
    if (typeof option == 'string') {
        option = url.parse(option);
    }

    return new promise(function(resolve, reject) {
        var req = http.request(option, function(res) {
            if (res.statusCode == 200) {
                resolve(res);
            } else {
                if (res.statusCode === 301 && res.headers.location) {
                    resolve(download(res.headers.location));
                } else {
                    reject(res.statusCode);
                }
            }
        })
        .on('error', function(e) {
            reject(e);
        })
        .end();
    });
}

download('http://localhost:8080/redirect')
.then(function(stream) {
    try {

        var writeStream = fs.createWriteStream('holyhigh.jpg');
        stream.pipe(writeStream);

    } catch(e) {
        console.error(e);
    }
});
wdanxna
  • 9,604
  • 2
  • 17
  • 23
5

Based on the other answers above and some subtle issues, here is my attempt.

  1. Check the file does not exist before hitting the network by using fs.access.
  2. Only create the fs.createWriteStream if you get a 200 OK status code. This reduces the amount of fs.unlink commands required to tidy up temporary file handles.
  3. Even on a 200 OK we can still possibly reject due to an EEXIST file already exists.
  4. Recursively call download if you get a 301 Moved Permanently or 302 Found (Moved Temporarily) redirect following the link location provided in the header.
  5. The issue with some of the other answers recursively calling download was that they called resolve(download) instead of download(...).then(() => resolve()) so the Promise would return before the download actually finished. This way the nested chain of promises resolve in the correct order.
  6. It might seem cool to clean up the temp file asynchronously, but I chose to reject only after that completed too so I know that everything start to finish is done when this promise resolves or rejects.
const https = require('https');
const fs = require('fs');

/**
 * Download a resource from `url` to `dest`.
 * @param {string} url - Valid URL to attempt download of resource
 * @param {string} dest - Valid path to save the file.
 * @returns {Promise<void>} - Returns asynchronously when successfully completed download
 */
function download(url, dest) {
  return new Promise((resolve, reject) => {
    // Check file does not exist yet before hitting network
    fs.access(dest, fs.constants.F_OK, (err) => {

        if (err === null) reject('File already exists');

        const request = https.get(url, response => {
            if (response.statusCode === 200) {
       
              const file = fs.createWriteStream(dest, { flags: 'wx' });
              file.on('finish', () => resolve());
              file.on('error', err => {
                file.close();
                if (err.code === 'EEXIST') reject('File already exists');
                else fs.unlink(dest, () => reject(err.message)); // Delete temp file
              });
              response.pipe(file);
            } else if (response.statusCode === 302 || response.statusCode === 301) {
              //Recursively follow redirects, only a 200 will resolve.
              download(response.headers.location, dest).then(() => resolve());
            } else {
              reject(`Server responded with ${response.statusCode}: ${response.statusMessage}`);
            }
          });
      
          request.on('error', err => {
            reject(err.message);
          });
    });
  });
}
Josh Peak
  • 4,110
  • 3
  • 33
  • 45
  • You shouldn't waste resources doing the download if the destination file already exists. Do the check first if possible. – Phil May 16 '21 at 21:09
  • @Phil Really good point. I have updated the example with an earlier guard check before falling into the recursive networking calls but left the success 200 file handling code the same. This short circuit guard statement should save some time in this case now. – Josh Peak May 17 '21 at 02:04
5

Modern version (ES6, Promise, Node 12.x+ ) works for https/http. ALso it supports redirects 302 & 301. I decided do not use 3rd party libraries due to it can be easy done with standard Node.js libs.

// download.js
import fs from 'fs'
import https from 'https'
import http from 'http'
import { basename } from 'path'
import { URL } from 'url'

const TIMEOUT = 10000

function download (url, dest) {
  const uri = new URL(url)
  if (!dest) {
    dest = basename(uri.pathname)
  }
  const pkg = url.toLowerCase().startsWith('https:') ? https : http

  return new Promise((resolve, reject) => {
    const request = pkg.get(uri.href).on('response', (res) => {
      if (res.statusCode === 200) {
        const file = fs.createWriteStream(dest, { flags: 'wx' })
        res
          .on('end', () => {
            file.end()
            // console.log(`${uri.pathname} downloaded to: ${path}`)
            resolve()
          })
          .on('error', (err) => {
            file.destroy()
            fs.unlink(dest, () => reject(err))
          }).pipe(file)
      } else if (res.statusCode === 302 || res.statusCode === 301) {
        // Recursively follow redirects, only a 200 will resolve.
        download(res.headers.location, dest).then(() => resolve())
      } else {
        reject(new Error(`Download request failed, response status: ${res.statusCode} ${res.statusMessage}`))
      }
    })
    request.setTimeout(TIMEOUT, function () {
      request.abort()
      reject(new Error(`Request timeout after ${TIMEOUT / 1000.0}s`))
    })
  })
}

export default download

Kudo to Andrey Tkachenko for his gist which I modified

Include it in another file and use

const download = require('./download.js')
const url = 'https://raw.githubusercontent.com/replace-this-with-your-remote-file'
console.log('Downloading ' + url)

async function run() {
  console.log('Downloading file')
  try {
    await download(url, 'server')
    console.log('Download done')
  } catch (e) {
    console.log('Download failed')
    console.log(e.message)
  }
}

run()
Roman Podlinov
  • 19,179
  • 7
  • 37
  • 56
3

If you are using express use res.download() method. otherwise fs module use.

app.get('/read-android', function(req, res) {
   var file = "/home/sony/Documents/docs/Android.apk";
    res.download(file) 
}); 

(or)

   function readApp(req,res) {
      var file = req.fileName,
          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");
          }
        });  
    }
KARTHIKEYAN.A
  • 11,752
  • 4
  • 81
  • 93
3

download.js (i.e. /project/utils/download.js)

const fs = require('fs');
const request = require('request');

const download = (uri, filename, callback) => {
    request.head(uri, (err, res, body) => {
        console.log('content-type:', res.headers['content-type']);
        console.log('content-length:', res.headers['content-length']);

        request(uri).pipe(fs.createWriteStream(filename)).on('close', callback);
    });
};

module.exports = { download };


app.js

... 
// part of imports
const { download } = require('./utils/download');

...
// add this function wherever
download('https://imageurl.com', 'imagename.jpg', () => {
  console.log('done')
});
williamsi
  • 942
  • 1
  • 9
  • 14
2

Path : img type : jpg random uniqid

    function resim(url) {

    var http = require("http");
    var fs = require("fs");
    var sayi = Math.floor(Math.random()*10000000000);
    var uzanti = ".jpg";
    var file = fs.createWriteStream("img/"+sayi+uzanti);
    var request = http.get(url, function(response) {
  response.pipe(file);
});

        return sayi+uzanti;
}
databilim
  • 29
  • 2
2

Using the http2 Module

I saw answers using the http, https, and request modules. I'd like to add one using yet another native NodeJS module that supports either the http or https protocol:

Solution

I've referenced the official NodeJS API, as well as some of the other answers on this question for something I'm doing. The following was the test I wrote to try it out, which worked as intended:

import * as fs from 'fs';
import * as _path from 'path';
import * as http2 from 'http2';

/* ... */

async function download( host, query, destination )
{
    return new Promise
    (
        ( resolve, reject ) =>
        {
            // Connect to client:
            const client = http2.connect( host );
            client.on( 'error', error => reject( error ) );

            // Prepare a write stream:
            const fullPath = _path.join( fs.realPathSync( '.' ), destination );
            const file = fs.createWriteStream( fullPath, { flags: "wx" } );
            file.on( 'error', error => reject( error ) );

            // Create a request:
            const request = client.request( { [':path']: query } );

            // On initial response handle non-success (!== 200) status error:
            request.on
            (
                'response',
                ( headers/*, flags*/ ) =>
                {
                    if( headers[':status'] !== 200 )
                    {
                        file.close();
                        fs.unlink( fullPath, () => {} );
                        reject( new Error( `Server responded with ${headers[':status']}` ) );
                    }
                }
            );

            // Set encoding for the payload:
            request.setEncoding( 'utf8' );

            // Write the payload to file:
            request.on( 'data', chunk => file.write( chunk ) );

            // Handle ending the request
            request.on
            (
                'end',
                () =>
                {
                    file.close();
                    client.close();
                    resolve( { result: true } );
                }
            );

            /* 
                You can use request.setTimeout( 12000, () => {} ) for aborting
                after period of inactivity
            */

            // Fire off [flush] the request:
            request.end();
        }
    );
}

Then, for example:

/* ... */

let downloaded = await download( 'https://gitlab.com', '/api/v4/...', 'tmp/tmpFile' );

if( downloaded.result )
{
    // Success!
}

// ...

External References

EDIT Information

  • The solution was written for typescript, the function a class method - but with out noting this the solution would not have worked for the presumed javascript user with out proper use of the function declaration, which our contributor has so promptly added. Thanks!
Rik
  • 384
  • 2
  • 8
  • Is this backward compatible? Works everywhere or only for http2? – Neil Dec 23 '20 at 11:21
  • @Neil I'm assuming you are asking if http2 is backward compatible with http1.2 - and the answer is no. Because http2 improves framing; adding binary compression, the ability to push from server to client, and simultaneous connections - it is absolutely required that both server and client know the implementation (this allows abstraction of implementation from the application too). Good news is that all major browsers have supported http2 since about 2015 - and Node as a client does too. Node, Nginx, and Apache offer it server side - so most use cases are covered. Its a vast improvement. – Rik Dec 28 '20 at 13:38
  • Thanks for the reply Rik – Neil Dec 28 '20 at 17:49
1

Without library it could be buggy just to point out. Here are a few:

Here my suggestion:

  • Call system tool like wget or curl
  • use some tool like node-wget-promise which also very simple to use. var wget = require('node-wget-promise'); wget('http://nodejs.org/images/logo.svg');
Geng Jiawen
  • 7,297
  • 2
  • 39
  • 36
1

Writing my own solution since the existing didn't fit my requirements.

What this covers:

  • HTTPS download (switch package to http for HTTP downloads)
  • Promise based function
  • Handle forwarded path (status 302)
  • Browser header - required on a few CDNs
  • Filename from URL (as well as hardcoded)
  • Error handling

It's typed, it's safer. Feel free to drop the types if you're working with plain JS (no Flow, no TS) or convert to a .d.ts file

index.js

import httpsDownload from httpsDownload;
httpsDownload('https://example.com/file.zip', './');

httpsDownload.[js|ts]

import https from "https";
import fs from "fs";
import path from "path";

function download(
  url: string,
  folder?: string,
  filename?: string
): Promise<void> {
  return new Promise((resolve, reject) => {
    const req = https
      .request(url, { headers: { "User-Agent": "javascript" } }, (response) => {
        if (response.statusCode === 302 && response.headers.location != null) {
          download(
            buildNextUrl(url, response.headers.location),
            folder,
            filename
          )
            .then(resolve)
            .catch(reject);
          return;
        }

        const file = fs.createWriteStream(
          buildDestinationPath(url, folder, filename)
        );
        response.pipe(file);
        file.on("finish", () => {
          file.close();
          resolve();
        });
      })
      .on("error", reject);
    req.end();
  });
}

function buildNextUrl(current: string, next: string) {
  const isNextUrlAbsolute = RegExp("^(?:[a-z]+:)?//").test(next);
  if (isNextUrlAbsolute) {
    return next;
  } else {
    const currentURL = new URL(current);
    const fullHost = `${currentURL.protocol}//${currentURL.hostname}${
      currentURL.port ? ":" + currentURL.port : ""
    }`;
    return `${fullHost}${next}`;
  }
}

function buildDestinationPath(url: string, folder?: string, filename?: string) {
  return path.join(folder ?? "./", filename ?? generateFilenameFromPath(url));
}

function generateFilenameFromPath(url: string): string {
  const urlParts = url.split("/");
  return urlParts[urlParts.length - 1] ?? "";
}

export default download;
zurfyx
  • 23,843
  • 15
  • 103
  • 130
0
function download(url, dest, cb) {

  var request = http.get(url, function (response) {

    const settings = {
      flags: 'w',
      encoding: 'utf8',
      fd: null,
      mode: 0o666,
      autoClose: true
    };

    // response.pipe(fs.createWriteStream(dest, settings));
    var file = fs.createWriteStream(dest, settings);
    response.pipe(file);

    file.on('finish', function () {
      let okMsg = {
        text: `File downloaded successfully`
      }
      cb(okMsg);
      file.end(); 
    });
  }).on('error', function (err) { // Handle errors
    fs.unlink(dest); // Delete the file async. (But we don't check the result)
    let errorMsg = {
      text: `Error in file downloadin: ${err.message}`
    }
    if (cb) cb(errorMsg);
  });
};
Alex Pilugin
  • 603
  • 2
  • 8
  • 28
0
var fs = require('fs'),
    request = require('request');

var download = function(uri, filename, callback){
    request.head(uri, function(err, res, body){
    console.log('content-type:', res.headers['content-type']);
    console.log('content-length:', res.headers['content-length']);
    request(uri).pipe(fs.createWriteStream(filename)).on('close', callback);

    }); 
};   

download('https://www.cryptocompare.com/media/19684/doge.png', 'icons/taskks12.png', function(){
    console.log('done');
});
Pankaj
  • 179
  • 1
  • 3
  • 9
0

Here's yet another way to handle it without 3rd party dependency and also searching for redirects:

        var download = function(url, dest, cb) {
            var file = fs.createWriteStream(dest);
            https.get(url, function(response) {
                if ([301,302].indexOf(response.statusCode) !== -1) {
                    body = [];
                    download(response.headers.location, dest, cb);
                  }
              response.pipe(file);
              file.on('finish', function() {
                file.close(cb);  // close() is async, call cb after close completes.
              });
            });
          }

Frankenmint
  • 1,304
  • 1
  • 15
  • 29
-1

You can try using res.redirect to the https file download url, and then it will be downloading the file.

Like: res.redirect('https//static.file.com/file.txt');

Jeremy M.
  • 1,043
  • 1
  • 6
  • 25
Yin
  • 9
-1

I've found this approach to be the most helpful especially when it comes to pdfs and random other files.

import fs from "fs";

  fs.appendFile("output_file_name.ext", fileDataInBytes, (err) => {
    if (err) throw err;
    console.log("File saved!");
  });
richbray89
  • 783
  • 9
  • 17
-2

I suggest you to use res.download same as follow:

app.get('/download', function(req, res){
  const file = `${__dirname}/folder/abc.csv`;
  res.download(file); // Set disposition and send it.
});
Mehrzad Tejareh
  • 591
  • 3
  • 17
-4
var requestModule=require("request");

requestModule(filePath).pipe(fs.createWriteStream('abc.zip'));
Bugs
  • 4,356
  • 9
  • 30
  • 39
  • 7
    Code dumps are generally not useful and may be downvoted or deleted. It would be worth editing to at least explain what the code is doing for future visitors. – Bugs Jun 01 '17 at 09:30