How to download and save an image using Node.js

By

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.

Tagged: Node.js · All topics
~~~

Related posts about node: