Downloading images in the browser with Node.js
Tags: node, images
Published: 2021-09-09 08:54:54

---
title: Downloading images in the browser with Node.js
published: true
description: A short guide on how to download images in modern browsers.
tags: Node, Images
//cover_image: https://direct_url_to_image.jpg
---

Image downloading in modern browsers seems like a topic trivial enough - why write about it?

### The drawback of native HTML downloading

HTML5 has a neat `download` attribute readily available for the anchor element - by simply adding the following, a user can easily view the image by clicking the link.

```html
<a href="https://picsum.photos/200/300" download="random.png">
  Download this image
</a>
```

The problem with this approach is that the image simply opens in the browser and requires the user to `save as`. This behaviour may not be the preferable user experience. A better flow may be that the user clicks the link and it automatically downloads into the Download Folder configured in the browser's settings.

This is also achievable without any server-side code in the following way:

_index.html_

```html
<button id="download-link">Download Image</button>
```

_index.js_

```js
const downloadButton = document.querySelector("#download-link");

downloadButton.addEventListener("click", async (evt) => {
  evt.preventDefault();

  // Fetch the image blob
  const url = "https://picsum.photos/200/300";
  const response = await fetch(url);
  const blob = await response.blob();

  // Create an objectURL
  const blobURL = URL.createObjectURL(blob);

  // create a hidden anchor element
  const anchor = document.createElement("a");
  anchor.style.display = "none";

  // Set the <a> tag's href to blob url
  // and give it a download name
  anchor.href = blobURL;
  anchor.download = "image-name.png";

  // Append anchor and trigger the download
  document.body.appendChild(anchor);
  anchor.click();
});
```

The client-side code above listens to a click on the HTML button, fetches the image as a blob, creates an objectURL, adds it to a newly created (hidden) anchor tag and clicks it to initiate a download. Because the anchor tag has an object URL, the browser will initiate the download to the user's Download Folder.

This experience may be more user-friendly, but don't be surprised if you run into the notorious `CORS` wall. `CORS` or Cross-Origin Resource Sharing may many times cause the download to fail from the browser if the resource is not on the same origin, or doesn't have the appropriate headers set.

### Making image download robust with Node.js

Luckily, for requests not coming from a browser e.g. a Node.js server - `CORS` can be safely bypassed. The following example only requires one simple change to the download logic on the client - the URL. Instead of making a fetch directly to the image, you will make it to your Node.js API endpoint, which could be set up as follows:

_app.js_
```js
const fetch = require("node-fetch");
const express = require("express");
const app = express();

app.get("/image", async (req, res) => {
  // Fetch the required image
  const imageURL = "https://picsum.photos/200/300";
  const response = await fetch(imageURL);

  // Set the appropriate headers, to let
  // the browser know that it should save
  res.writeHead(200, {
    "content-disposition": 'attachment; filename="my-image.png"',
    "content-type": "image/png",
  });

  // Pipe the request buffer into
  // the response back to the client
  return response.body.pipe(res);
});
```

The example above has a few parts to it, namely:

- Requesting the known image URL to receive the raw body in the response. The URL here could be dynamically set too and that way you could simply prepend your server URL to _any_ image URL, e.g.

```js
app.get("/image/:url", (req, res) => {
  const { url } = req.params;
  // ...
});
```

Just remember to encode the URI on the client before appending it to your server URL, e.g.

```js
const finalURL = `https://your-server.com/image/${encodeURIComponent(url)}`;
```

- Setting the appropriate headers for the response:

`content-dispostion` with a value of `attachment` will tell the browser to save the file instead of the alternative `inline` which will try to render the response in the browser.

Note here too you might want to have some sort of library or checker to determine the image MIME type e.g. `image/png` for the `content-type` header and file extension to be accurate.

- Piping the result into the response:

This simply takes the data in the result body and feeds it into the body of the response to the client.

#### Serverless Caveat

If you're using a serverless solution, be mindful of their Request Payload size limits. E.g. AWS limits the size of request bodies to ~6MB. If you're working with large images, consider a static solution.

### Conclusion

If you're already calling a Node.js back-end to feed your front-end, why not add an endpoint to help you download remote images with a better experience. You even get the niceties of overcoming the dreaded `CORS` error.

---

_If you want to automate this task for your website screenshots, let [Stillio](https://www.stillio.com) do the heavy lifting._