How to download and save an image using Node.js
By Flavio Copes
Learn how to download and save an image in Node.js with Axios by requesting it as a stream and piping the response into a file write stream you can await.
To download and save an image in Node.js, request it with Axios as a stream and pipe the response into a file write stream. I had the need of downloading a file from the Internet, and I also wanted to use await to do something else after in an easy way. This is the pattern I use.
Why a stream? An image can be big. With the default response type, Axios buffers the whole response in memory before you can touch it. With a stream, the data flows from the network straight into the file, a chunk at a time.
Start with the imports:
import fs from 'node:fs'
import axios from 'axios'
Then write a download() function like this:
async function download(url, filepath) {
const response = await axios({
url,
method: 'GET',
responseType: 'stream',
})
return new Promise((resolve, reject) => {
response.data
.pipe(fs.createWriteStream(filepath))
.on('error', reject)
.once('close', () => resolve(filepath))
})
}
With responseType: 'stream', response.data is a readable stream. We pipe it into a write stream created with fs.createWriteStream().
The tricky part is knowing when the file is done. pipe() returns right away, before any data is written. So we wrap the streams in a promise: when the write stream emits close, the file is fully on disk and we resolve. If it emits error, we reject.
Because the function returns a promise, you can await it:
const url = 'https://flaviocopes.com/img/og.png'
await download(url, './images/og.png')
console.log('saved!')
The console.log only runs after the image is saved. That was the whole point for me: do the next thing only when the download is complete.
Two things that can go wrong
First, the destination folder must exist. fs.createWriteStream('./images/og.png') fails with an ENOENT error if there’s no images folder. Create it first:
fs.mkdirSync('./images', { recursive: true })
The recursive option also makes this a no-op when the folder already exists.
Second, Axios rejects on HTTP errors. If the URL returns a 404, await axios(...) throws before any file is written. Wrap the call in a try/catch so a broken URL doesn’t crash your script:
try {
await download(url, './images/og.png')
} catch (error) {
console.error('download failed:', error.message)
}
This same function works for any file, not just images. PDFs, zip archives, videos: the stream doesn’t care what the bytes are.
Related posts about node: