Unlocking Smooth Downloads: A Guide to User-Friendly File Download After User Actions (POST Requests)
Tags: webdev, api
Published: 2024-03-25 13:34:12

**What Happens When You Click "Download"**

First, let's understand what occurs when you click a "Get File" or "Download File" button on a browser. It turns out it's simply a basic `GET` API call. We call the URL using the `href` attribute on an `<a>` tag, and it returns a file, which we can consider a blob. By adding the `download` attribute to the `<a>` tag, we can simply download that blob. 

```Html
<a href="path/to/your/file.pdf" download="myfile.pdf">Download File</a>
```

The browser then intercepts the download and shows progress, speed, and the estimated time remaining (ETA), creating a great user experience (UX). The user is updated and doesn't get confused at any point.

Here's an example:

{% embed [https://youtu.be/vXvrjpBuiEg](https://youtu.be/vXvrjpBuiEg) %}

**The Issue with Uploading Data**

Now, sometimes we need to send some data to the server to manipulate a file, usually to create a new one. Here's where things get tricky. To do this, you need to make a `POST` request. The server receives the request, performs the necessary tasks, and then sends the file. So far, so good.

**The Problem with Downloading After POST**

To download the file after a `POST` request, we typically do something like this:

```Javascript
const response = await fetch(
  url,
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ name: "John Doe" }),
  }
);
const blob = await response.blob();
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = "file.zip";
link.target = "_blank";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
```

**There's a problem with this approach, though.** The browser's download manager doesn't intercept the download. Users have no idea when the file is ready, when the download started, what the speed is, or the estimated time remaining (ETA). **This might be okay for tiny files, but it's a terrible experience for files larger than even 5 MB.**

Here's an example of this issue:

{% embed [https://youtu.be/QzkjROtBSZw](https://youtu.be/QzkjROtBSZw) %}

**The Fix: Storing and Downloading Later**

So, how can we generate files on demand but still allow the browser to handle the download, displaying progress, ETA, and maybe even enabling parallel downloading?

The fix is simple: you have to store the file somewhere and send the link to the user. Then, just open it in a new tab.

```Javascript
const response = await fetch(
  "url",
  {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ name: "John Doe" }),
  }
);
const url = await response.text();

window.open(url, "_blank");
```

This way, the file is handled by the wonderful browser!

Here's an example of this solution:

{% embed [https://youtu.be/b0G3PxDxDzM](https://youtu.be/b0G3PxDxDzM) %}

**Try it Yourself!**

The examples I used are hosted here: [https://post-request-blog.vercel.app/](https://post-request-blog.vercel.app/) You can try them out and fully understand what I mean. Feel free to snoop around in the network tab for the `POST` requests to get a better grasp of the process.

**Code Repository**

Here's the GitHub repository for the code: [https://github.com/Shreyaan/post-request-blog](https://github.com/Shreyaan/post-request-blog)

**Simplifications**

For simplicity, I took some shortcuts, such as not using a proper backend with Node.js or Python and instead using Next.js. In a real-world scenario, a public folder on the backend would be used to store the file. Additionally, I'm using the first API's URL as the URL returned by the third API. Ideally, the third API would return the URL of the generated and stored file in the public folder.

I hope you enjoyed this and learned something new!