How to download an image using Node.js
By Flavio Copes
Learn how to download an image or file in Node.js with the built-in fetch API and fs, piping the response into createWriteStream to save it to disk.
To download an image in Node.js, request it with the built-in fetch API (Node 18+) and write the body to disk with fs.createWriteStream(). No extra HTTP library needed.
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 use today:
import fs from 'fs'
import { Readable } from 'stream'
import { pipeline } from 'stream/promises'
const download = async (url, path) => {
const response = await fetch(url)
if (!response.ok) {
throw new Error(`Download failed: ${response.status}`)
}
await pipeline(
Readable.fromWeb(response.body),
fs.createWriteStream(path)
)
}
const url = 'https://flaviocopes.com/img/avatar.png'
const path = './images/avatar.png'
await download(url, path)
console.log('✅ Done!')
fetch ships with Node.js since version 18. You do not install anything for this path.
If the file is small and you prefer a buffer, this also works:
const response = await fetch(url)
const buffer = Buffer.from(await response.arrayBuffer())
fs.writeFileSync(path, buffer)
The stream + pipeline version is better for larger files. The whole image is never held in memory at once.
How does it work?
fetch(url) returns a Response. response.body is a Web ReadableStream.
Readable.fromWeb() turns that into a Node.js readable stream. fs.createWriteStream(path) creates a writable stream pointing at a file on disk.
pipeline() connects the two and forwards errors for you. Chunks of the image are written to the file as they come in over the network.
You can inspect headers before writing if you need to:
const response = await fetch(url)
console.log(response.headers.get('content-type'))
console.log(response.headers.get('content-length'))
That is the same idea as the old request.head() call, without a separate package.
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. pipeline() rejects on a dropped connection, so wrap the call in try/catch if you need the download to fail gracefully.
What about the old request package?
Older tutorials (including an earlier version of this post) used the request module and piped it into createWriteStream(). That package has been deprecated for years. Prefer built-in fetch.
If you want a third-party HTTP client instead, Axios works fine for downloads too. And if you are sending data the other way, see how to make an HTTP POST request in Node.js.
Want me to talk about your product? You can sponsor this site.
Related posts about node: