How to download an image using Node.js
By Flavio Copes
Learn how to download an image or file in Node.js using the request module and fs, piping the response into createWriteStream to save it to disk.
To download an image in Node.js, request it from the server and pipe the response into a write stream created with fs.createWriteStream(). The file is saved to disk as the data arrives.
I asked myself this question when I had to download a file from a server, programmatically.
I had to connect to a server, download a file, and store it locally.
This is the code I used:
const fs = require('fs')
const request = require('request')
const download = (url, path, callback) => {
request.head(url, (err, res, body) => {
request(url)
.pipe(fs.createWriteStream(path))
.on('close', callback)
})
}
const url = 'https://flaviocopes.com/img/avatar.png'
const path = './images/avatar.png'
download(url, path, () => {
console.log('✅ Done!')
})
The code uses the fs built-in module and the request module.
request must be installed:
npm install request
How does it work?
The interesting part is the piping.
request(url) returns a readable stream of the response body. fs.createWriteStream(path) creates a writable stream pointing at a file on disk.
pipe() connects the two. Chunks of the image are written to the file as they come in over the network. The whole image is never held in memory at once, which matters when the file is large.
The close event fires when the write stream is done, so that’s where we call the callback.
The initial request.head() call fetches only the headers of the resource, without the body. You can use it to check the response before committing to the download, for example inspecting res.headers['content-type'] or the content length. If you don’t need that, you can skip it and call request(url) directly.
A pitfall: the destination folder must exist
fs.createWriteStream() creates the file, but not the folders in the path.
If the ./images folder does not exist, the download fails with an ENOENT error. Create it first:
fs.mkdirSync('./images', { recursive: true })
The recursive option makes it a no-op when the folder is already there, so it’s safe to call every time.
Also consider handling errors on the streams. A dropped connection emits an error event, and without a listener it crashes the process. Add .on('error', ...) on the request if you need the download to fail gracefully.
Note that the request module was recently deprecated, which means it’s “complete” and no new changes will be applied to it. Only fixes. It doesn’t mean it will stop working and it does not mean we should stop using it.
Related posts about node: