How to download an image from URL in Node

By

Learn how to download an image from a URL in Node.js using the https module, streaming the response into a write stream with pipe() and handling redirects.

~~~

To download an image from a URL in Node.js, request it with the built-in https module and stream the response into a file using fs.createWriteStream() and pipe(). You don’t need any third-party package.

This is the full code:

import fs from 'fs'
import https from 'https'

function downloadImage(url, filePath) {
  return new Promise((resolve, reject) => {
    https
      .get(url, (response) => {
        const code = response.statusCode ?? 0

        if (code >= 400) {
          return reject(new Error(response.statusMessage))
        }

        //handle redirects
        if (code > 300 && code < 400 && response.headers.location) {
          return resolve(downloadImage(response.headers.location, filePath))
        }

        //save the file to disk
        const fileWriter = fs.createWriteStream(filePath)

        fileWriter.on('finish', () => resolve(filePath))
        fileWriter.on('error', reject)

        response.pipe(fileWriter)
      })
      .on('error', reject)
  })
}

await downloadImage('https://flaviocopes.com/img/og.png', './og.png')

Why streams?

An image can easily be a few megabytes. pipe() writes each chunk to disk as it arrives from the network, so the whole file never sits in memory.

That’s a big deal when you download many images in parallel, like in a scraper or a build script that fetches remote assets.

The function wraps everything in a promise, so you can await it. When the write stream fires its finish event, the file is fully on disk and we resolve with the file path.

Why handle redirects?

Many image URLs don’t point directly at the file. A CDN might answer with a 301 or 302 and a Location header pointing at the real address.

https.get() does not follow redirects for you. That’s what the middle check does: if the status code is in the 3xx range and there’s a Location header, we call downloadImage() again with the new URL.

We also reject on any status code of 400 or higher. Without that check, a missing image would save the 404 error page to disk with a .png name, and you’d only find out later when the file won’t open.

A common pitfall

Listen for finish on the write stream, not for end on the response. The response can end while the last chunk is still being flushed to disk. Resolve too early and you might read a truncated image.

Also attach an error handler to the write stream. Without it, a disk problem (a folder that doesn’t exist, missing permissions) crashes the process instead of rejecting the promise.

Tagged: Node.js · All topics
~~~

Related posts about node: